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