4.09.2012

RTC DS1307 i STM32F4Discovery

Podłączamy układ RTC, aby mieć zawsze aktualną datę i czas. Najprościej użyć właśnie wspomnianego układu DS1307 na magistrali I2C. Na internecie bez problemu znajdziemy biblioteki do obsługi tego układu. Moja biblioteka zawiera dodatkowe funkcje, ale o tym za chwile. Na razie schemat jak podłączyć. Wyświetlacz LCD został z projektu termometru na OneWire. Na pewno się przyda.

STM32F4Discovery DS1307
STM32F4Discovery DS1307

Składowe daty i czasu w rejestrach układu DS1307 przechowywane są w formacie BCD, więc zaczynamy od pomocniczych funkcji umieszczonych w projekcie Common. Jedna zamienia BCD na bajt, a druga konwertuje bajt na BCD.

public static class ByteExtension
{
    public static byte FromBCD(this byte @this)
    {
        var result = (byte) (((@this & 0xf0) >> 4)*10 + (@this & 0x0f));
        return result;
    }

    public static byte ToBCD(this byte @this)
    {
        return (byte) (@this/10 << 4 | @this%10);
    }
}

W konstruktorze klasy DS1307 tworzymy i konfigurujemy I2CDevice. Tworzymy również pomocnicze funkcje do zapisywania i odczytywania danych z rejestrów przy pomocy urządzenia I2C utworzonego w konstruktorze.

public DS1307()
{
    var config = new I2CDevice.Configuration(Address, ClockRate);
    _device = new I2CDevice(config);
}

private byte[] ReadRam(byte address, int expectedBytes)
{
    var result = new byte[expectedBytes];
    var transactions = new I2CDevice.I2CTransaction[2];

    transactions[0] = I2CDevice.CreateWriteTransaction(new[] {address});
    transactions[1] = I2CDevice.CreateReadTransaction(result);

    int actual = _device.Execute(transactions, Timeout);
    int expected = 1 + expectedBytes;

    if (actual != expected)
        throw new IOException("Unexpected I2C transaction result");

    return result;
}

private void WriteRam(byte address, params byte[] data)
{
    if (data == null) 
        throw new ArgumentNullException("data");

    byte[] buffer = Utility.CombineArrays(new[] {address}, data);
    I2CDevice.I2CWriteTransaction transaction = I2CDevice.CreateWriteTransaction(buffer);
    var transactions = new I2CDevice.I2CTransaction[] {transaction};

    int actual = _device.Execute(transactions, Timeout);
    int expected = buffer.Length;

    if (actual != expected)
        throw new IOException("Unexpected I2C transaction result");
}

Dzięki takim pomocniczym funkcjom procedury zapisu i odczytu daty i czasu z DS1307 są bardzo proste.

public DateTime GetDateTime()
{
    byte[] buffer = ReadRam(SecondsAddr, 7);

    byte value = buffer[SecondsAddr];
    bool halted = (value & 0x80) > 0; //CH bit is set
    if (halted)
        throw new InvalidOperationException("DS1307 halted");

    byte second = value.FromBCD();
    byte minute = buffer[MinutesAddr].FromBCD();

    value = buffer[HoursAddr];
    bool mode12 = (value & 0x40) > 0; //12-hour mode
    if (mode12)
        throw new InvalidOperationException("DS1307 in 12-hour mode");

    byte hour = value.FromBCD();
    byte day = buffer[DateAddr].FromBCD();
    byte month = buffer[MonthAddr].FromBCD();
    int year = 2000 + buffer[YearAddr].FromBCD();

    var result = new DateTime(year, month, day, hour, minute, second);
    return result;
}

public void SetDateTime(DateTime dateTime)
{
    var second = (byte) dateTime.Second;
    var minute = (byte) dateTime.Minute;
    var hour = (byte) dateTime.Hour;
    var dayOfWeek = (byte) (dateTime.DayOfWeek + 1);
    var day = (byte) dateTime.Day;
    var month = (byte) dateTime.Month;
    var year = (byte) (dateTime.Year - 2000);

    var buffer = new[]
                     {
                         second.ToBCD(),
                         minute.ToBCD(),
                         hour.ToBCD(),
                         dayOfWeek.ToBCD(),
                         day.ToBCD(),
                         month.ToBCD(),
                         year.ToBCD()
                     };

    WriteRam(SecondsAddr, buffer);
}

Układ DS1307 posiada przestrzeń do przechowywania dowolnych informacji. Możemy tam przechowywać np. konfigurację. Dlatego zrobiłem dodatkowe funkcje do zapisu i odczytu danych z tej przestrzeni. Ostatnie dwie funkcje, to przykład jak możemy w tej przestrzeni przechowywać własne dane. Akurat tutaj na przykładzie string, ale z innymi typami (czy nawet obiektami) jest podobnie. Specjalne dwa markery na początku tej przestrzeni RAM informują nas czy są tam poprawne dane (po resecie na pewna będą losowe wartości). Kolejny bajt przechowuje rozmiar danych.

public void WriteRam(byte[] data)
{
    if (data == null) 
        throw new ArgumentNullException("data");

    int len = data.Length;
    const int maxDataLen = RamLength - 3; // 2*Marker + length = 3
    if(len > maxDataLen) 
        throw new InvalidOperationException("Only " + maxDataLen + " bytes allowed");

    data = Utility.CombineArrays(new[] {OkMarker, OkMarker, (byte)data.Length}, data);
    WriteRam(RamAddr, data);
}

public byte[] ReadRam()
{
    byte[] buffer = ReadRam(RamAddr, RamLength);
    if(buffer[0] != OkMarker || buffer[1] != OkMarker)
        return new byte[0];

    byte len = buffer[2];
    byte[] result = Utility.ExtractRangeFromArray(buffer, 3, len);
    return result;
}

public void WriteRamString(string data)
{
    byte[] buffer = Reflection.Serialize(data, typeof(string));
    WriteRam(buffer);
}

public string ReadRamString()
{
    byte[] buffer = ReadRam();
    if(buffer.Length == 0)
        return String.Empty;

    var result = (string) Reflection.Deserialize(buffer, typeof(string));
    return result;
}

Jak już mamy bibliotekę, to teraz trzeba jakoś zainicjować DS1307 aktualną datą i czasem. No niestety z komputera nie da się magicznie pobrać takich danych do płytki. Można by było to zrobić przez interfejs np. RS232 lub ethernet, ale na razie takich nie mamy. Najprościej to zrobić tak: ustawimy w programie stałą datę i czas, ale z przyszłości tzn. dodajemy minutę lub dwie. Ten czas będzie potrzebny na kompilację i wgranie programu. Czekamy aż nadejdzie pora i naciskamy userbutton. Dokładność ustawionego czasu będzie zależna od naszego refleksu. Taki program inicjujący uruchamiamy tylko raz. Dodatkowo, dla przykładu, do nieulotnej przestrzeni RAM wgrywamy tą ustawianą wartość (DateTime). Później dzięki niej łatwo policzymy czas pracy RTC (uptime).

public class Program
{
    public static void Main()
    {
        //set current date and time + 1 or 2 minutes
        var newDateTime = new DateTime(2012, 09, 04, 21, 30, 45);

        Debug.Print("Wait for " + newDateTime);

        using (var userButton = new InterruptPort(Stm32F4Discovery.ButtonPins.User,
                                                  false, Port.ResistorMode.PullDown,
                                                  Port.InterruptMode.InterruptEdgeLow))
        {
            var ds1307 = new DS1307();
            byte[] storeData = Reflection.Serialize(newDateTime, typeof (DateTime));
            ds1307.WriteRam(storeData);
            //push userbutton when time comes
            userButton.OnInterrupt += (d1, d2, t) =>
                                          {
                                              ds1307.SetDateTime(newDateTime);
                                              Debug.Print("Initialized");
                                          };

            Thread.Sleep(Timeout.Infinite);
        }
    }
}

Do ustawienia w systemie aktualnej daty i czasu służy procedura Utility.SetLocalTime(dt). Jeśli w taki sposób ustawimy czas przy starcie naszego program, to bieżący czas będziemy mogli pobrać zawsze przez DateTime.Now. Wyświetlenie takiej wartości na LCD to już trywialna sprawa. Dodatkowo po naciśnięciu userbutton pokazuje się przez pewien okres czas pracy RTC (uptime).

public class Program
{
    private const int Columns = 16;
    private const int ShowUptimeInterval = 10; //seconds

    private static readonly DS1307 Ds1307 = new DS1307();

    public static void Main()
    {
        DateTime dt = Ds1307.GetDateTime();
        Utility.SetLocalTime(dt);

        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(Columns, 2);

        var userButton = new InterruptPort(Stm32F4Discovery.ButtonPins.User,
                                           false, Port.ResistorMode.PullDown,
                                           Port.InterruptMode.InterruptEdgeLow);

        DateTime showUptimeMode = DateTime.MinValue;
        userButton.OnInterrupt += (d1, d2, t) => showUptimeMode = DateTime.Now
                                                                      .AddSeconds(ShowUptimeInterval);
        
        for (;;)
        {
            var now = DateTime.Now;

            string line1, line2;

            if(showUptimeMode > now)
            {
                TimeSpan uptime = GetUptime();
                string uptimeStr = uptime.ToString();
                int endIndex = uptimeStr.LastIndexOf('.');
                if(endIndex > Columns)
                    endIndex = Columns;

                line1 = "Uptime:   ";
                line2 = uptimeStr.Substring(0, endIndex);
            }
            else
            {
                line1 = now.ToString("yyyy-MM-dd");
                line2 = now.ToString("HH:mm:ss        ");
            }

            lcd.SetCursorPosition(0, 0);
            lcd.Write(line1);
            lcd.SetCursorPosition(0, 1);
            lcd.Write(line2);

            Thread.Sleep(100);
        }
    }

    private static TimeSpan GetUptime()
    {
        TimeSpan result = TimeSpan.MinValue;

        byte[] store = Ds1307.ReadRam();
        if (store.Length > 0)
        {
            var setTime = (DateTime) Reflection.Deserialize(store, typeof (DateTime));
            result = DateTime.Now - setTime;
        }

        return result;
    }
}

Pełny kot: DemoDS1307Init i DemoDS1307

Brak komentarzy:

Prześlij komentarz