PICSim.js - часы реального времени DS1307

Микросхема DS1307 представляет из себя часики с календарем. Всё :) Минимальная схема подключения состоит из кварца с частотой 32768 Гц и автономного источника питания. Если верить производителю, батарейки 48мАч должно хватить на 10 лет непрерывной работы часов. Обмен данными между DS1307 и внешним миром происходит по последовательному интерфейсу I2C, который как известно использует всего две двунаправленные линии связи (SDA и SCL), при этом на одной I2C шине может находиться до 127 различных устройств. Микросхема DS1307 в терминах I2C всегда является ведомой (Slave), т.е. не может инициировать обмен данными. Ведущим (Master) в нашем случае будет микроконтроллер PIC18F4620, у которого на борту имеется аппаратный I2C.

К ознакомлению настоятельно рекомендуется официальная документация на DS1307. В случае с шиной I2C в первую очередь важны следующие характеристики - максимальная частота тактирования SCL (от этого зависит максимальная скорость передачи данных), физический адрес микросхемы (из 127 возможных) на шине и внутренняя карта памяти (адреса регистров). Так как I2C является синхронным интерфейсом, то имеет значение именно верхняя граница частоты, тогда как фактическая скорость обмена задаётся мастером и может быть любой и даже необязательно постоянной - микроконтроллер может спокойно переключаться на более важные задачи без потери данных на шине. Это серьёзное преимущество синхронных интерфейсов переда асинхронными.

config.h | i2c-hw.c | liquid-crystal.c | hex | picsim.js

screenshot

Работа с символьным дисплеем описывалась в предыдущем материале. Специфические делали реализации работы с аппаратным I2C для данного микроконтроллера помещены в отдельный файл i2c-hw.c. Чтение данных из DS1307 происходит в функции rtc_r. По спецификации I2C ведомому разрешается придерживать (выставлять в 0) линию SCL, тем самым сигнализируя о неготовности к обмену данными - поэтому функция i2c_idle ждёт пока не отпустят SCL. С каждым чтением очередного байта i2c_rb внутренний адрес карты памяти DS1307 инкриминируется, что позволяет последовательно считывать минуты...год без явного указывания их адресов - нужен только начальный адрес секунд. В сети достаточно материала с подробным описанием интерфейса I2C, из которых назначение функций i2c_open, i2c_start и т.д. должно быть предельно ясным.

rtc_r.c

#include <stdint.h>
#include <stdbool.h>
#include <time.h>

#include "i2c-hw.c"

static uint8_t bcd2dec(const uint8_t bcd) {
  return ((bcd / 16) * 10) + (bcd % 16);
}

// http://microchip.wikidot.com/i2c:sequential-read

void rtc_r(struct tm * const t) {
  i2c_open();              // Open I2C module as master, low speed

  i2c_idle();              // Ensure I2C is idle
  i2c_start(false);        // I2C Start condition
  i2c_wb(0xD0);            // DS1307 slave I2C address + I2C write flag
  i2c_idle();              // Ensure I2C is idle
  i2c_wb(0);               // Sequential read start address

  i2c_idle();              // Ensure I2C is idle
  i2c_start(true);         // Restart condition
  i2c_wb(0xD1);            // DS1307 slave I2C address + I2C read flag

  i2c_idle();              // Ensure I2C is idle
  t->tm_sec = bcd2dec(i2c_rb(true)); // secs, I2C ACK condition

  i2c_idle();              // Ensure I2C is idle
  t->tm_min = bcd2dec(i2c_rb(true)); // minutes, I2C ACK condition

  i2c_idle();              // Ensure I2C is idle
  t->tm_hour = bcd2dec(i2c_rb(true)); // hours, I2C ACK condition

  i2c_idle();              // Ensure I2C is idle
  t->tm_wday = i2c_rb(true); // Day of Week, I2C ACK condition

  i2c_idle();              // Ensure I2C is idle
  t->tm_mday = bcd2dec(i2c_rb(true)); // Day of Month, I2C ACK condition

  i2c_idle();              // Ensure I2C is idle
  t->tm_mon = bcd2dec(i2c_rb(true)); // month, I2C ACK condition

  i2c_idle();              // Ensure I2C is idle
  t->tm_year = bcd2dec(i2c_rb(false)); // year, I2C NACK condition (end of sequential read)

  t->tm_yday = t->tm_isdst = -1; // less than zero if the information is not available

  i2c_stop_and_close();    // I2C Stop condition, then close I2C module
}

main.c

/*
  xc8 --chip=18f4620 main.c
*/

#define _XTAL_FREQ 25e4

#include <xc.h>
#include <stdio.h>
#include <time.h>
#include <stdint.h>
#include "config-4620.h"

#include "liquid-crystal.c"
#include "rtc_r.c"

// http://microchip.wikidot.com/faq:29
void putch(const uint8_t byte) {
  lcd_dat(byte);
}

int main() {
  // lcd
  PORTD = TRISD = 0;
  PORTE = TRISE = 0;
  // i2c
  TRISC3 = TRISC4 = 1;

  lcd_init();
  lcd_no_cursor();
  lcd_no_blink();

  while(1) {
    const char * const daysOfWeek[7] = {
      "Sun.", "Mon.", "Tues.", "Wed.", "Thurs.", "Fri.", "Sat."
    };
    struct tm t;

    lcd_home();
    rtc_r(&t);
    printf("%02d:%02d:%02d  %s",
      t.tm_hour, t.tm_min, t.tm_sec,
      t.tm_wday >= 0 && t.tm_wday < 7 ? daysOfWeek[t.tm_wday] : ":(");
    lcd_set_cursor(0, 1);
    printf("%02d/%02d/%02d", t.tm_mday, t.tm_mon, t.tm_year);
  }

  return 0;
}

Текущую время и дату можно конечно же выставлять - адреса внутренней карты памяти DS1307 для записи такие же как и на чтение. Если скорость обмена данными не имеет особого значения, то ничто не мешает заменить аппаратный I2C (файл i2c-hw.c) программной реализацией (программно считывать/выставлять ноги микроконтроллера SCL/SDA) - это также проверялось и даже будет работать в симуляции PICSim.js.

Далее сопрограммы.

links

social