28.08.2012

Termometr z LCD na STM32F4Discovery

Bez rozłączania poprzedniego układu (z DS18B20), możemy do płytki podłączyć wyświetlacz LCD. Najprościej podłączyć LCD zgodny z HD44780. Ja akurat miałem pod ręką YM1602C. Łączymy według schematu poniżej.
HD44780 + STM32F4Discovery

HD44780 + STM32F4Discovery
No dobra. Zapalamy wyświetlacz. Tylko jak? na szczęście ktoś już za nas zrobił bibliotekę do obsługi takich wyświetlaczy: μLiquidCrystal. Aha. Jest jeden problem. Jak już ściągniemy źródła, to trzeba w projekcie μLiquidCrystal zmienić framework na 4.2 (domyślnie jest 4.1 bodajże). No to start. Testujemy LCD krótkim programikiem.

var lcdProvider = new GpioLcdTransferProvider(Stm32F4Discovery.Pins.PD1, Stm32F4Discovery.Pins.PD2,
                                              Stm32F4Discovery.Pins.PD9, Stm32F4Discovery.Pins.PD11,
                                              Stm32F4Discovery.Pins.PD10, Stm32F4Discovery.Pins.PD8);

var lcd = new Lcd(lcdProvider);
lcd.Begin(16, 2); //columns, rows

//znaki specjalne
//http://www.quinapalus.com/hd44780udg.html
var customCharacters = new[]
                           {
                               new byte[] {0x00, 0x0a, 0x15, 0x11, 0x11, 0x0a, 0x04, 0x00}, //serce
                               new byte[] {0x04, 0x02, 0x01, 0x1f, 0x01, 0x02, 0x04, 0x00} //strzalka
                           };

//ladowanie znakow specjalnych
for (int i = 0; i < customCharacters.Length; i++)
    lcd.CreateChar(i, customCharacters[i]);

lcd.Clear();
lcd.Write("* Hello World! *");
Thread.Sleep(3000);

lcd.Clear();
lcd.WriteByte(0); //pierwszy znak specjalny
Thread.Sleep(2000);
lcd.WriteByte(1); //drugi znak specjalny
Thread.Sleep(3000);

//nastepna linia
lcd.SetCursorPosition(0, 1);
lcd.Write("#     Bye...   #");

 Pełny kot: DemoLcd

Na wyświetlaczu powinny pojawić się jakieś napisy. No niestety polskich znaków nie wyświetlimy. Jak wszystko jest OK to robimy kolejny display do termometru z poprzedniego postu. Najważniejsza jest procedura, która wyświetla temperaturę.
public void ShowTemperature(float temperature)
{
    _lcd.SetCursorPosition(0, 0);
    string txt = "Temperatura:";
    int padsCnt = Columns - txt.Length;
    _lcd.Write(txt + new string(' ', padsCnt));

    _lcd.SetCursorPosition(0, 1);

    string tempStr = temperature.ToString("F2");
    padsCnt = Columns - tempStr.Length;
    _lcd.Write(tempStr);
    _lcd.WriteByte(0);

    if (padsCnt > 0)
    {
        var pads = new String(' ', padsCnt);
        _lcd.Write(pads);
    }
}

Dodajemy jeszcze LCD do listy wyświetlaczy.

public Display()
{
    var debugDisplay = new DebugDisplay();
    var ledDisplay = new FourLedDisplay();
    var lcd = new LcdDisplay();
    _displays = new IDisplay[] {debugDisplay, ledDisplay, lcd};
}

W taki oto sposób mamy termometr z 3 wyświetlaczami: w oknie debug, na diodach z płytki i na LCD.

Pełny kot: DemoDS18B20

26.08.2012

Termometr na DS18B20 (1-Wire) i STM32F4Discovery

DS18B20 + STM32F4Discovery
DS18B20 + STM32F4Discovery
W internecie jest wiele bibliotek do DS18B20 i wiele przykładów jak pobierać informację o temperaturze. Moja biblioteka radzi sobie dobrze z wartościami ujemnymi i obsługuje zarówno DS18B20 jak i DS18S20. Poniżej zamieszczam tylko samo sedno biblioteki, czyli metody do odczytu temperatury z czujnika i konwersji na wartość.

Komunikacja z DSem musi odbywać się według ustalonej sekwencji: inicjalizacja (reset linii 1-Wire), wysłanie komendy ROM (np. match=0x55), wysłanie funkcji. Poniżej pomocnicza metoda, która taką sekwencję uruchamia.
private void RunSequence(byte command)
{
    if(_bus.TouchReset() == 0) //0 = no devices, 1 = device(s) exist
        throw new IOException("DS18X20 communication error");

    Write(MatchROMCommand);
    Write(_presensePulse);
    Write(command);
}

private void Write(params byte[] sendValues)
{
    foreach (byte sendValue in sendValues)
        _bus.WriteByte(sendValue);
}

Właściwa funkcja odczytu temperatury wysyła 2 komendy. Pierwsza inicjuje pomiar temperatury, a druga go pobiera. Właściwie to układ zwraca 2 bajty, które trzeba dopiero na temperaturę zamienić. Wyliczenie wartości temperatury uzależnione jest od typu układu i uwzględnia znak.
public float GetTemperature()
{
    RunSequence(ConvertTCommand);

    DateTime timeBarier = DateTime.Now.AddMilliseconds(TimeoutMiliseconds);
    while(_bus.ReadByte() == 0)
    {
        if(DateTime.Now > timeBarier)
            throw new IOException("DS18X20 read timeout");
    }

    RunSequence(ReadScratchpadCommand);

    int reading = _bus.ReadByte() | (_bus.ReadByte() << 8); //lsb msb
    bool minus = (reading & 0x8000) > 0;
    if (minus)
        reading = (reading ^ 0xffff) + 1; //uzupelnienie do 2 (U2)

    float result = _sSeries
                       ? CalculateS(reading)
                       : CalculateB(reading);
    if(minus)
        result = -result;

    return result;
}

private static float CalculateB(int reading)
{
    float result = 6*reading + reading/4; // multiply by (100 * 0.0625) or 6.25
    result = result/100;
    return result;
}

private static float CalculateS(int reading)
{
    float result = (reading & 0x00FF)*0.5f;
    return result;
}

No dobra. Czas temperaturę wyświetlić. Wyświetlacz będzie implementował interfejs IDisplay (od razu dla przykładu najprostsza implementacja):
public interface IDisplay
{
    void ShowTemperature(float temperature);
    void ShowError(string message);
}

public class DebugDisplay : IDisplay
{
    public void ShowTemperature(float temperature)
    {
        Debug.Print("Temperatura: " + temperature.ToString("F2") + " °C");
    }

    public void ShowError(string message)
    {
        if (message == null) 
            throw new ArgumentNullException("message");

        Debug.Print("Error: " + message);
    }
}

Dodajemy jeszcze jedną klasę pomocniczą implementującą IDisplay. Z niej będziemy korzystali w programie. Dzięki temu, w przyszłości, łatwo będzie można dodać nowe wyświetlacze. Można sobie nawet wyobrazić taką sytuację, że w zależności od np. ustawionych zworek raz będzie działał jeden wyświetlacz, a raz inny.
public class Display : IDisplay
{
    private readonly IDisplay[] _displays;

    public Display()
    {
        var debugDisplay = new DebugDisplay();
        _displays = new IDisplay[] {debugDisplay};
    }

    public void ShowTemperature(float temperature)
    {
        foreach (IDisplay display in _displays)
            display.ShowTemperature(temperature);
    }

    public void ShowError(string message)
    {
        if (message == null) 
            throw new ArgumentNullException("message");

        foreach (IDisplay display in _displays)
            display.ShowError(message);
    }
}

Teraz wszystko trzeba poskładać w całość. Na początku głównej procedury programu mamy inicjalizację OneWire i wykrycie czujników. W moim przypadku zawsze jeden, więc bierzemy pierwszy lepszy.
var op = new OutputPort(Stm32F4Discovery.FreePins.PA15, false);
var ow = new OneWire(op);

IDisplay display = new Display();
DS18X20[] devices = DS18X20.FindAll(ow);
if (devices.Length == 0)
{
    display.ShowError("Brak DS18B20");
    return;
}

DS18X20 tempDev = devices[0];

Samo sedno programu stanowi kot wykonywany w nieskończoność, który pobiera temperaturę i wyświetla wartość na wyświetlaczu. Dzięki przechwyceniu wyjątku jest odporny na błędy transmisji (np. wyciągnięcie kabelka sygnałowego).

for (;;)
{
    float currentTemp = 0;
    string exceptionMsg = String.Empty;
    try
    {
        currentTemp = tempDev.GetTemperature();
    }
    catch(IOException ex)
    {
        exceptionMsg = ex.Message;
    }

    if (exceptionMsg.Length == 0)
        display.ShowTemperature(currentTemp);
    else
        display.ShowError(exceptionMsg);

    Thread.Sleep(1000);
}

Jeśli wszystko mamy dobrze podłączone i .NET MF z OneWire w STM32F4Discovery, to otrzymamy w oknie debug informacje o aktualnej temperaturze:

Temperatura: 26.75 °C
Temperatura: 26.81 °C

Możemy też skonstruować i dodać do programu drugi wyświetlacz - na diodach LED, które znajdują się na płytce. Każda kolejna dioda będzie się zapalać po przekroczeniu określonej temperatury. Najciekawszym elementem klasy jest chyba konstruktor, w którym określamy progi zapalania się poszczególnych diod oraz procedura realizująca wyświetlanie temperatury.
public FourLedDisplay()
{
    var blueLed = new OutputPort(Stm32F4Discovery.LedPins.Blue, false);
    var greenLed = new OutputPort(Stm32F4Discovery.LedPins.Green, false);
    var orangeLed = new OutputPort(Stm32F4Discovery.LedPins.Orange, false);
    var redLed = new OutputPort(Stm32F4Discovery.LedPins.Red, false);

    _ranges = new[]
                  {
                      new LedRange(blueLed, 20),
                      new LedRange(greenLed, 25),
                      new LedRange(orangeLed, 30),
                      new LedRange(redLed, 35)
                  };
}

public void ShowTemperature(float temperature)
{
    if(_timer != null)
        _timer.Change(Timeout.Infinite, BlinkPeriod);

    foreach (LedRange range in _ranges)
        range.Check(temperature);
} 

Nowy wyświetlacz musimy jeszcze tylko dodać do listy w konstruktorze klasy Display.
public Display()
{
    var debugDisplay = new DebugDisplay();
    var ledDisplay = new FourLedDisplay();
    _displays = new IDisplay[] {debugDisplay, ledDisplay};
}

Pełny kot: DemoDS18B20

21.08.2012

OneWire i STM32F4Discovery - mówisz i masz

Kompilacja PK opanowana. Czas dodać 1-Wire. Bułka z masłem. Żeby się za dużo nie narobić trzeba zlikwidować błędy w PK (wersja QFE2). Otwieramy (może być notatnik lub inny zaawansowany edytor xml) plik C:\MicroFrameworkPK_v4_2\Framework\Features\Analog_DA_HAL.libcatproj. Guid w linijce Guid zmieniamy z  54DABCD1-8C9D-485c-8C48-8ECEE7D27454 na

00E400EA-0018-00CA-B59A-A2B3F9586139

Jest to ten sam guid z linijki MFComponent z pliku C:\MicroFrameworkPK_v4_2\DeviceCode\Drivers\Stubs\Processor\stubs_DA\dotNetMF.proj. Po prostu w pliku Analog_DA_HAL.libcatproj jest pomylony.

Następnie otwieramy plik C:\MicroFrameworkPK_v4_2\Framework\Features\OneWire_PAL.libcatproj i w linijce z StubLibrary zmieniamy guid z 24E1C771-7E4F-471C-A85C-78D693C259B6 na

238A3F72-46C6-4267-88BE-D15C09594103

A ten guid odpowiada ProjGuid z pliku C:\MicroFrameworkPK_v4_2\DeviceCode\pal\OneWire\Stubs\dotNetMF.proj (stubs to nic innego jak zaślepki). Nie wiem skąd się wziął w oryginale guid od zaślepki piezo? (C:\MicroFrameworkPK_v4_2\DeviceCode\pal\piezo\stubs\dotNetMF.proj).

W tym samym pliku zmieniamy jeszcze jeden guid. W linijce FeatureAssociations jest guid C9D30638-8B83-42B7-90A6-C96899B185F1 projektu SD (z pliku C:\MicroFrameworkPK_v4_2\Framework\Features\SD.featureproj) zamiast guidu

3401dd69-cab5-45fc-a759-1d1eded247c7

z projektu C:\MicroFrameworkPK_v4_2\Framework\Features\OneWire.featureproj.

Uff! Najgorsze za nami. Odpalamy program C:\MicroFrameworkPK_v4_2\tools\bin\SolutionWizard\SolutionWizard.exe i nic nie zmieniamy...

... aż do tego miejsca. Tutaj zaznaczamy OneWire.
Wygląd jak na obrazku poniżej oznacza, że wszystko jest ok. Tutaj zaznaczmy "Show All Choices" i ...
... patrzymy (lub zaznaczmy) czy jest wybrany dobry pal.
To wszystko. SolutionWizard zmienił dokładnie 2 pliki: C:\MicroFrameworkPK_v4_2\Solutions\Discovery4\TinyBooter\TinyBooter.proj (uzupełnił stub od analaog_DA) oraz C:\MicroFrameworkPK_v4_2\Solutions\Discovery4\TinyCLR\TinyCLR.proj (dodał wpisy od OneWire). Kto ciekawy co się zmieniło niech sobie porówna. Kompilujemy PK dokładnie tak samo jak wcześniej opisałem. W wyniku otrzymujemy pliki:
Wgrywamy je na płytkę przy pomocy ST_LINK i MFDeploy, tak jak opisałem wcześniej. Czas na test. Uruchamiamy ponownie krótki programik:
var op = new OutputPort(Stm32F4Discovery.FreePins.PA15, false);
var ow = new OneWire(op);

ArrayList devices = ow.FindAllDevices();
Debug.Print("Found " + devices.Count);
Wynik w postaci: Found 0 bez żadnego wyjątku to bardzo dobry znak! OneWire powinno działać bez problemu. Montujemy więc szybko układ z czujnikiem temperatury DS18B20.

1-wire stm32f4discovery
Uruchomienie programu takiego jak powyżej pokaże Found 1. OneWire działa poprawnie.

Jeden kabelek USB

Denerwujące jest podłączanie dwóch kabelków USB do STM32F4Discovery. Teoretycznie USB CN1 jest potrzebne tylko podczas wgrywania pliku tinybooter za pomocą ST-LINK. Praktycznie zawsze musi być podpięte, bo z tego gniazda brane jest zasilanie. Ale istnieje na to sposób. Wystarczy podłączyć PA9 do +5V, jak na rysunku poniżej:
stm32f4discovery
Teraz wystarczy, że będziemy podpinać USB CN5 i wszystko powinno ładnie działać. Nie wiem tylko czy podłączenie na raz CN1 i CN5 jest bezpieczne. Na wszelki wypadek ja wpinam albo CN1 albo CN5 - nigdy obydwa, bo i nie mam na razie takiej potrzeby.

20.08.2012

Kompilacja Porting Kit dla STM32F4Discovery

Postanowiłem skompilować PK. Na razie czysty, bez żadnych zmian, aby nabrać wprawy i zobaczyć jak to wszystko pójdzie.

Podstawową sprawą jest użycie do kompilacji Keil MDK-ARM w wersji Standard. Dla wersji 4.12 na 100% poprawnie się wszystko skompiluje. Program instalujemy na dysku C, reszta standardowo. Powinna powstać taka struktura katalogów: C:\Keil\ARM. Następnie trzeba ściągnąć i zainstalować Porting Kit 4.2 (RTM QFE2). PK również trzeba zainstalować na dysku C (powstanie katalog C:\MicroFrameworkPK_v4_2). Teraz trzeba ściągnąć NETMF for STM32 (F4 Edition) Release 4.2 QFE2 RTM i wypakować wszystko jak idzie do katalogu PK, tak aby nadpisać lub dodać nowe pliki i katalogi. Nas interesuje katalog C:\MicroFrameworkPK_v4_2\Solutions\Discovery4.

PK jest przygotowane do kompilacji. Otwieramy okno konsoli cmd i przechodzimy do katalogu C:\MicroFrameworkPK_v4_2. Uruchamiamy polecenie:  setenv_mdk 4.12
Teraz można odpalić właściwą kompilację poleceniem:

msbuild /p:flavor=DEBUG;memory=FLASH Solutions\Discovery4\dotNetMF.proj

Zamiast DEBUG można podać RELEASE - zależy którą wersję chce się otrzymać. Rozpocznie się kompilacja.
Na koniec zobaczymy podsumowanie. Nie powinno być żadnych błędów.
Interesujące są 3 pliki:
  1. C:\MicroFrameworkPK_v4_2\BuildOutput\THUMB2FP\MDK4.12\le\FLASH\debug\Discovery4\bin\Tinybooter.hex
  2. C:\MicroFrameworkPK_v4_2\BuildOutput\THUMB2FP\MDK4.12\le\FLASH\debug\Discovery4\bin\tinyclr.hex\ER_CONFIG
  3. C:\MicroFrameworkPK_v4_2\BuildOutput\THUMB2FP\MDK4.12\le\FLASH\debug\Discovery4\bin\tinyclr.hex\ER_FLASH
Pliki wgrywamy standardowo, zgodnie z opisem w: Przygotowanie STM32F4Discovery do pracy.
Jeśli kompilujemy od nowa trzeba usunąć katalog C:\MicroFrameworkPK_v4_2\BuildOutput.

16.08.2012

Nowy NET MF i port dla STM32F4

Dwa dni temu ukazała się nowa wersja .NET Micro Framework (.NET MF 4.2 RTM QFE2), a dzisiaj wersja NETMF dla STM32 F4

W wersji QFE2 dodano obsługę WinUSB oraz wyjścia analogowego. Poprawiono, miedzy innymi, błędy w StringBuilder.Append, StreamReader, File.Exists, VolumeInfo.Format oraz inne błędy związane z File System i SerialPort. Zaimplementowano AppDomain.GetAssemblies i przyspieszono działanie niektórych składników. Zmieniono też sposób przygotowania do kompilacji PK - jeden plik do ustawiania zmiennych.

1.08.2012

OneWire i STM32F4Discovery

Dzisiaj postanowiłem podłączyć czujnik temperatury DS18B20 do STM32F4Discovery. Niestety moje próby spełzły na niczym, ponieważ pliki hex (pliki binarne z NETMF) dla tej płytki ściągnięte ze strony http://netmf4stm32.codeplex.com nie zawierają obsługi protokołu 1-Wire. Możemy się o tym przekonać uruchamiając malutki programik:
var op = new OutputPort(Stm32F4Discovery.FreePins.PA15, false);
var ow = new OneWire(op);

ArrayList devices = ow.FindAllDevices();
W wyniku otrzymamy:

An unhandled exception of type 'System.NotSupportedException' occurred in Microsoft.SPOT.Hardware.OneWire.dll

Wiedziałem, że trzeba będzie kiedyś spróbować skompilować PK (Porting Kit) samemu, ale nie myślałem, że nastąpi to tak szybko. Na razie stopień skomplikowania PK mnie odstrasza.