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