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

Brak komentarzy:

Prześlij komentarz