poniedziałek, 22 maja 2017

MCP79410 - zegarek od Microchipa - doskonała alternatywa dla DS1307.

Szczęśliwi czasu nie liczą. Zgodnie z tą zasadą od wielu lat nie używam zegarka poza domem. W domu jednak zegarek się przydaje aby skontrolować np. czas do rozpoczęcia filmu w TV czy też zsynchronizować się z czasem pobudki porannej.
Mój pierwszy domowy zegarek zbudowałem w oparciu o DS1307 i mikrokontroler Atmega 8 efekt końcowy wyglądał tak :



Swoją drogą wyświetlanie czasu na wyświetlaczu LED ma swój urok i magię. DS1307 nie jest może szczególnie dokładny ale przez ponad 2 lata użytkowania nie odczułem zbytnio tej niedogodności. Zaletą DS1307 była m.in prostota oprogramowania i bezproblemowa dostępność poza tym ten zegarek ma to coś co skłania do polubienia go. W świecie napięć 3.3 V niestety zegarek ten nie miał racji bytu. Stąd moje poszukiwania alternatywy dla niego. Z natury nie lubię podążać za tłumem a ten wskazywał jedynie słuszny kierunek w postaci - DS3231. Super dokładny z kompensacją temperatury , cud techniki niemalże. Jest w tym wypadku pewien nurt , który trochę mnie zniechęcił do zaprzyjaźnienia się z tym zegarkiem..
Mianowicie wszechobecna dostępność chińskich klonów tego zegarka, które z rynku skutecznie wyparły oryginały !!!. Efektem jest pogarszająca się dostępność zegarka DS3231 w oryginale na rzecz chińskich klonów i czasami zaporowa wręcz cena rzędu 60 zł (ceny oscylują w zakresie 20-60 zł) za szt podczas gdy cena klona to np 5-12 zł.
Do chińszczyzny zarówno kulinarnej jak i elektronicznej oferowanej w naszym kraju mam awersję. Może dlatego , że przeraża mnie świadomość jakich chemikaliów używa się do produkcji chińskiej elektroniki podróbkowej  i już sam kontakt przez skórę tych związków degraduje nasze zdrowie. Dlatego tam gdzie mogę to ograniczam swój kontakt z chińskimi podróbkami.

Bohater artykułu czyli zegarek MCP79410, występuje tylko w oryginale, nie znalazłem podróbek na rynku może dlatego, że jego cena w oryginale jest tak niska, że nie opłaca się tego podrabiać i to jest wielka zaleta tego produktu. Zegarek kupiłem w popularnym sklepie elektronicznym za całe 4,5 zł, dla porównania DS1307 był dostępny w tym samym sklepie za 6.20 zł. Można śmiało powiedzieć, że MCP79410 jest jednym z najtańszych zegarków na rynku występujących w oryginale i doskonale nadającym się do odmierzania czasu :) Pomijam milczeniem w tym przypadku układ PCF8563T, którego dokładność jest chyba najgorsza ze wszystkiego co tyka.



Należy wspomnieć o zagadnieniu dotyczącym kwarca. Mianowicie zegarki od Microchipa są optymalizowane do kwarców o pojemności 6-9 pF. Obecne u nas Chińskie kwarce mają pojemność 12.5 pF. Kwarce rekomendowane przez Microchipa renomowanych firm znalazłem bez problemu w firmie zagranicznej sprzedającej części elektroniczne na naszym rynku. Taki dedykowany kwarc zapewni nam dokładność tykania.


Miłym akcentem jest support od Microchipa dla MCP79410 m.in w postaci gotowej biblioteki w języku C. Biblioteka jest przygotowana dla kobyły - płytki ewaluacyjnej Explorer 16
Spróbujemy zaadaptować ten kod do własnych potrzeb.

Zegarek posiada bardzo bogatą dokumentację. Co ciekawe mamy możliwość zwiększenia dokładności, przy wykorzystaniu zewnętrznego czujnika temperatury i to również jest wyczerpująco opisane w dokumentacji.

Do zabawy z zegarkiem wykorzystam :
  •  mikrokontroler PIC24HJ128GP502 (na tę chwilę mój standardowy  mikrokontroler 16 bitowy),
  •  wyświetlacz LCD 4x20 firmy Electronic Assembly serii DOGM204A(szybki jak wiatr wschodni)
  •  zegarek MCP79410 przylutowany do płytki konwertera SO8 --> DIP8
  •  płytki stykowe kabelki, zasilanie 3.3 V etc


Trzeba jeszcze wspomnieć, że mikrokontroler PIC24HJ128GP502 ma na pokładzie swój zegarek RTC w standardzie więc zewnętrznych mu nie trzeba. No ale czymś musimy przecież zagadać z MCP79410.

Czas ładnie się prezentuje na wyświetlaczu LCD DOGM204A :


Zegarek odpalił od przysłowiowego kopa i jest to kolejny element od Microchipa , który raczej polubię.
Jedynym utrudnieniem dla początkujących może być konwersja z kodu BCD na DEC i maskowanie zbędnych bitów rejestrów aby wyświetlić poprawnie cyfry daty i czasu na wyświetlaczu LCD. W tym przypadku należy uzupełnić sobie wiedzę o kodowaniu BCD, przesuwaniu bitowym i operacjach bitowych. Kiedyś jak byłem zielony to wydawało mi się to nie do ogarnięcia ale małymi kroczkami do przodu i można nawet Chińczyka zrozumieć :)

Struktura programu :

Pliki  nagłówkowe programu :            

ustaw_zegar.h
dogm204.h 
i2c.h 
i2c_rtcc.h 

Pliki  wykonawcze programu :

ustaw_zegar.c (dotyczy ustawień zegara systemowego mikrokontrolera)
dogm204.c (dotyczy wyświetlacza LCD DOGM204)
i2c.c (dotyczy wysyłania/odbierania danych po magistrali I2C)
i2c_rtcc.c (dotyczy zapisu i odczytu danych po magistrali I2C do/z MCP79410)
main.c (pobieramy dane z zegarka, konwertujemy z BCD na DEC i na CHAR, wyświetlamy czas i datę na wyświetlaczu)

Kluczowym zagadnieniem przy wyświetlaniu czasu jest synchronizacja pomiędzy wyświetlaniem czasu a jego aktualizacją. Problem ten rozwiązuje nam  wyjście MFP zegarka, które generuje przebieg o częstotliwości 1Hz. Na opadającym zboczu tego sygnału wyzwalamy przerwanie zewnętrzne mikrokontrolera INT0.
W funkcji obsługi przerwania INT0 ustawiamy flagę programową (int0_flag), za pomocą której aktywujemy odświeżanie czasu na wyświetlaczu.

W programie dla uproszczenia pominąłem obsługę ustawienia czasu przez użytkownika np za pomocą zewnętrznych klawiszy, podczerwieni, fal mózgowych :), enkodera etc.
W bibliotece od Microchipa jest obsługa dwóch klawiszy i ustawienie czasu, można się tym przykładem posłużyć.

W pliku nagłówkowym i2c_rtcc.h mamy wyczerpane wszystkie możliwe definicje dotyczące zegarka MCP79410 i zawartości jego rejestrów, dlatego ten plik jest na bogato.

Uwaga !!! pomimo , że na schemacie uwzględniłem podłączenie baterii to w testach na płytce stykowej nie podłączałem jej i stąd wynika dezaktywacja funkcji ini_i2c_rtcc () w pliku main.c .Funkcja ta ustawia odpowiedni rejestr zegarka do współpracy z baterią zewnętrzną.

Jeśli podłączymy baterię funkcję tą trzeba wykonać.

Podsumowując zabawę z MCP79410, należy stwierdzić co następuje. Za zalety uważam łatwą dostępność przez długie lata czego gwarantem jest firma Microchip. Brak chińskich klonów. Niska cena.
Dedykowane kwarce , dzięki którym zegarek jest względnie dokładny. Łatwa implementacja kompensacji temperaturowej co jest bardzo dobrze opisane w dodatkowej dokumentacji od Microchipa. Szeroki zakres  napięcia zasilania 1.8 V - 5,5 V. Dodatkowa wewnętrzna pamięć do dyspozycji użytkownika 64 bajty SRAM i 128 bajtów EEPROM. Dwa niezależne alarmy współdziałające z wyściem MFP. Przykładowe programy w języku C. Inne nie wymienione.

Uważam ten zegarek za bardzo dobry wybór dla systemów z zasilaniem +3.3 V.




/******************************************************************************* 
                            File:   ustaw_zegar.h
 ******************************************************************************/

#ifndef USTAW_ZEGAR_H
#define USTAW_ZEGAR_H

#define FCY 40000000 /* podajemy wartosc ustawionego zegara (40 MHz), wazne 
aby przed includowaniem <libpic30.h>, potrzebne to jest do wyliczania delay-i*/
/*deklaracja funkcji*/
void ustaw_zegar(void) ;

#endif  /* USTAW_ZEGAR_H */

/******************************************************************************* 
                                 File:   i2c.h
 ******************************************************************************/
extern unsigned char err_flg       ;  /*error flag ; reacts at slave's wrong NACK*/
void ini_i2c1(void)                ;  // init the I2C module
void i2c_start(void)               ;  // generates an I2C start condition
void i2c_restart(void)             ;  // generates an I2C Restart condition (for reads)
void i2c_stop(void)                ;  // generates an I2C stop condition
void i2c_wr(unsigned char i2c_data);  // writes a byte to the I2C bus
unsigned char i2c_rd(void)         ;  // reads a byte from the I2C bus
void i2c_ack(void)                 ;  // generates a Master Acknowledge
void i2c_nack(void)                ;  // generates a Master No Acknowledge
void poll_tim1(void)               ;   /*obsluga bledow*/     

/*******************************************************************************
* FileName:        i2c_rtcc.h
* Processor:       PIC24HJ128GP502 
* Hardware:        MCP79410 I2C RTCC
* Compiler:        MPLAB XC16 
*******************************************************************************/
void i2c_rtcc_wr(unsigned char rtcc_reg, unsigned char data); // writes data to  the I2C RTCC   
unsigned char i2c_rtcc_rd(unsigned char rtcc_reg)           ; // reads data from the I2C RTCC
void ini_i2c_rtcc(void)                                     ; // init the I2C RTCC
void ini_i2c_time(void)                                     ; // init ime/date vars in the RTCC
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//                   GLOBAL CONSTANTS RTCC - REGISTERS ADDRESSES 
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     #define  ADDR_EEPROM_WRITE 0xae       //  DEVICE ADDR for EEPROM (writes)   
     #define  ADDR_EEPROM_READ  0xaf       //  DEVICE ADDR for EEPROM (reads)  
     #define  ADDR_RTCC_WRITE   0xde       //  DEVICE ADDR for RTCC MCHP  (writes) 
     #define  ADDR_RTCC_READ    0xdf       //  DEVICE ADDR for RTCC MCHP  (reads) 
//..............................................................................  
     #define  SRAM_PTR          0x20       //  pointer of the SRAM area (RTCC) 
     #define  ADDR_EEPROM_SR    0xff       //  STATUS REGISTER in the  EEPROM
//..............................................................................
     #define  ADDR_SEC          0x00       //  address of SECONDS      register 
     #define  ADDR_MIN          0x01       //  address of MINUTES      register 
     #define  ADDR_HOUR         0x02       //  address of HOURS        register 
     #define  ADDR_DAY          0x03       //  address of DAY OF WK    register 
     #define  ADDR_STAT         0x03       //  address of STATUS       register 
     #define  ADDR_DATE         0x04       //  address of DATE         register  
     #define  ADDR_MNTH         0x05       //  address of MONTH        register 
     #define  ADDR_YEAR         0x06       //  address of YEAR         register 
     #define  ADDR_CTRL         0x07       //  address of CONTROL      register 
     #define  ADDR_CAL          0x08       //  address of CALIB        register 
     #define  ADDR_ULID         0x09       //  address of UNLOCK ID    register
//..............................................................................
     #define  ADDR_ALM0SEC      0x0a       //  address of ALARM0 SEC   register 
     #define  ADDR_ALM0MIN      0x0b       //  address of ALARM0 MIN   register 
     #define  ADDR_ALM0HR       0x0c       //  address of ALARM0 HOUR  register 
     #define  ADDR_ALM0CTL      0x0d       //  address of ALARM0 CONTR register
     #define  ADDR_ALM0DAT      0x0e       //  address of ALARM0 DATE  register 
     #define  ADDR_ALM0MTH      0x0f       //  address of ALARM0 MONTH register 
//.............................................................................. 
     #define  ADDR_ALM1SEC      0x11       //  address of ALARM1 SEC   register 
     #define  ADDR_ALM1MIN      0x12       //  address of ALARM1 MIN   register 
     #define  ADDR_ALM1HR       0x13       //  address of ALARM1 HOUR  register 
     #define  ADDR_ALM1CTL      0x14       //  address of ALARM1 CONTR register
     #define  ADDR_ALM1DAT      0x15       //  address of ALARM1 DATE  register 
     #define  ADDR_ALM1MTH      0x16       //  address of ALARM1 MONTH register 
//..............................................................................       
     #define  ADDR_SAVtoBAT_MIN 0x18       //  address of T_SAVER MIN(VDD->BAT)
     #define  ADDR_SAVtoBAT_HR  0x19       //  address of T_SAVER HR (VDD->BAT) 
     #define  ADDR_SAVtoBAT_DAT 0x1a       //  address of T_SAVER DAT(VDD->BAT) 
     #define  ADDR_SAVtoBAT_MTH 0x1b       //  address of T_SAVER MTH(VDD->BAT) 
//..............................................................................
     #define  ADDR_SAVtoVDD_MIN 0x1c       //  address of T_SAVER MIN(BAT->VDD)
     #define  ADDR_SAVtoVDD_HR  0x1d       //  address of T_SAVER HR (BAT->VDD) 
     #define  ADDR_SAVtoVDD_DAT 0x1e       //  address of T_SAVER DAT(BAT->VDD) 
     #define  ADDR_SAVtoVDD_MTH 0x1f       //  address of T_SAVER MTH(BAT->VDD)        
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//                  GLOBAL CONSTANTS RTCC - INITIALIZATION 
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     #define  PM                0x20       //  post-meridian bit (ADDR_HOUR) 
     #define  OUT_PIN           0x80       //  = b7 (ADDR_CTRL) 
     #define  SQWE              0x40       //  SQWE = b6 (ADDR_CTRL) 
     #define  ALM_NO            0x00       //  no alarm activated        (ADDR_CTRL) 
     #define  ALM_0             0x10       //  ALARM0 is       activated (ADDR_CTRL)
     #define  ALM_1             0x20       //  ALARM1 is       activated (ADDR_CTRL)
     #define  ALM_01            0x30       //  both alarms are activated (ADDR_CTRL)
     #define  MFP_01H           0x00       //  MFP = SQVAW(01 HERZ)      (ADDR_CTRL)  
     #define  MFP_04K           0x01       //  MFP = SQVAW(04 KHZ)       (ADDR_CTRL)  
     #define  MFP_08K           0x02       //  MFP = SQVAW(08 KHZ)       (ADDR_CTRL)  
     #define  MFP_32K           0x03       //  MFP = SQVAW(32 KHZ)       (ADDR_CTRL)  
     #define  MFP_64H           0x04       //  MFP = SQVAW(64 HERZ)      (ADDR_CTRL)
     #define  ALMx_POL          0x80       //  polarity of MFP on alarm  (ADDR_ALMxCTL)   
     #define  ALMxC_SEC         0x00       //  ALARM compare on SEC      (ADDR_ALMxCTL)              
     #define  ALMxC_MIN         0x10       //  ALARM compare on MIN      (ADDR_ALMxCTL)   
     #define  ALMxC_HR          0x20       //  ALARM compare on HOUR     (ADDR_ALMxCTL)   
     #define  ALMxC_DAY         0x30       //  ALARM compare on DAY      (ADDR_ALMxCTL)   
     #define  ALMxC_DAT         0x40       //  ALARM compare on DATE     (ADDR_ALMxCTL)   
     #define  ALMxC_ALL         0x70       //  ALARM compare on all param(ADDR_ALMxCTL)    
     #define  ALMx_IF           0x08       //  MASK of the ALARM_IF      (ADDR_ALMxCTL)
     #define  OSCON             0x20       //  state of the oscillator(running or not)
     #define  VBATEN            0x08       //  enable battery for back-up  
     #define  START_32KHZ       0x80       //  start crystal: ST = b7 (ADDR_SEC)
     #define  LP                0x20       //  mask for the leap year bit(MONTH REG)   
     #define  HOUR_12           0x40       //  12 hours format   (ADDR_HOUR)


/******************************************************************************* 
                             File:   dogm204.h         
 ******************************************************************************/
#ifndef LCD_H
#define LCD_H

/*_TRISB3 --> TRISBbits.TRISB3*/
#define TRIS_RESET   _TRISA0
#define TRIS_RW      _TRISB3
#define TRIS_RS      _TRISA2
#define TRIS_E       _TRISB2
#define TRIS_DB4     _TRISB12
#define TRIS_DB5     _TRISB13
#define TRIS_DB6     _TRISB14
#define TRIS_DB7     _TRISB15
/*_RB3 --> PORTBbits.RB3*/
#define RESET   _RA0
#define RW      _RB3
#define RS      _RA2
#define E       _RB2
#define DB4     _RB12
#define DB5     _RB13
#define DB6     _RB14
#define DB7     _RB15

/* przyporzadkowanie adresow pamieci DD-RAM do pol wyswietlacza*/
#define LCD_Line1 0x00 /*adres 1 znaku 1 wiersza */
#define LCD_Line2 0x20 /*adres 1 znaku 2 wiersza */
#define LCD_Line3 0x40 /*adres 1 znaku 3 wiersza */
#define LCD_Line4 0x60 /*adres 1 znaku 4 wiersza */

 void Wyslij_do_LCD(unsigned char bajt);
 void WlaczLCD();
 void WyswietlLCD(char *napis);
 void UstawKursorLCD(uint8_t y, uint8_t x);
 void CzyscLCD();
 void WpiszSwojeZnaki(void);
#endif  /* LCD_H */


/*************************************************************************** 
                            File:   ustaw_zegar.c 
***************************************************************************/
/*Ustawiamy zegar wewnetrzny na ok 40 MHz (dokladnie na 39.61375 MHz*/
#include "xc.h" /* wykrywa rodzaj procka i includuje odpowiedni plik 
naglówkowy "p24HJ128GP502.h"*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h> /*dyrektywy do uint8_t itp*/
#include "ustaw_zegar.h" /*z uwagi na FCY musi byc przed #include <libpic30.h>*/

/*definicja funkcji*/
void ustaw_zegar(void) {
/*
* to co mozemy ustawic za pomoca '#pragma' jest dostepne w pliku 
* xc16/docs/config_index.html
*/
#pragma config JTAGEN = OFF
// Watchdog timer enable/disable by user software
#pragma config FWDTEN = OFF 

//********************Start Ustawien Zegara*********************************
/* 
* Fcy - zegar instrukcji , Fosc - zegar rdzenia (jest zawsze dwa razy wiekszy 
* od zegara instrukcji)) Ustawiamy zegar instrukcji na 40 Mhz z wewnetrznego 
* oscylatora Fin=7.37 MHz w/g wzoru Fcy=Fosc/2 gdzie Fosc=Fin x (M/(N1+N2))
* gdzie M=43, N2=2, N1=2 ustawiane w rejestrach PLLFBD/PLLPOST/PLLPRE
*/
//Select Internal FRC (Fast RC Oscillator)
#pragma config FNOSC = FRC // FOSCSEL-->FNOSC=0b000 (Fast RC Oscillator (FRC))
//Enable Clock Switching and Configure
#pragma config FCKSM = CSECMD //FOSC-->FCKSM=0b01 - wlacz zegar
#pragma config OSCIOFNC = OFF //FOSC-->OSCIOFNC=1 - Fcy b?dzie na pinie OSCO

/*Config PLL prescaler, PLL postscaler, PLL divisor*/
PLLFBD = 41 ; //M=43 (0 bit to 2 stad 41 = 43 patrz w rejestrze), tutaj 3.685 x 43 = 158.455MHz
CLKDIVbits.PLLPRE=0 ;  //N1=2, tutaj 7.37 MHz / 2 = 3.685 MHz
CLKDIVbits.PLLPOST=0 ; //N2=2, tutaj 158.455 MHz / 2 = 79.2275 MHz (Fosc)
/*   
* UWAGA przerwania musza byc wylaczone podczas wywolywania ponizszych 
* dwóch funkcji __builtin_write_...brak definicji w pliku naglówkowym
* to wewnetrzne funkcje kompilatora patrz help M-LAB IDE
* i datasheet str 140(11.6.3.1 Control Register Lock)
*/
/*Initiate Clock Switch to Internal FRC with PLL (OSCCON-->NOSC = 0b001)*/
__builtin_write_OSCCONH(0x01); //tutaj argumentem jest wartosc z NOSC
/*Start clock switching*/
 __builtin_write_OSCCONL(0x01);

/*Wait for Clock switch to occur*/
while(OSCCONbits.COSC !=0b001);

/*Wait for PLL to lock*/
while(OSCCONbits.LOCK !=1) {};

}


/******************************************************************************* 
                             File: i2c_func.c   
 ******************************************************************************/
#include "xc.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h> /*dyrektywy uint8_t itp*/
#include <string.h>
#include "ustaw_zegar.h" /*tutaj m.in ustawione FCY*/
#include <libpic30.h> /*biblioteka dajaca dostep do delay-i,musi byc po zaincludowaniu ustaw_zegar.h*/
#include "i2c.h"
//..............................................................................
// Init the I2C1 module
//..............................................................................
unsigned char err_flg;
/*definicje funkcji*/
void ini_i2c1(void){
    /*pin 17(SCL) i 18(SDA) sa cyfrowe na starcie, nie trzeba tutaj wylaczac wejsc analogowych*/
    I2C1BRG = 391 ; /*przy FCY ok 40 MHz dla bitrate 100 kHz I2C1BRG = 391 dla 400 kHz I2C1BRG=91*/
    I2C1STAT = 0x0000/*Status Register - pasuje wszystko na zero */
    I2C1CON = 0x1200/*Control Register - Relase SCL clock / Slew rate control disabled / 7-bit slave address*/ 
    /*zerujemy rejestr nadawczy i odbiorczy*/
    I2C1RCV = 0;
    I2C1TRN = 0;
    /*Enable I2C1 module*/
    I2C1CONbits.I2CEN = 1;
   }
//..............................................................................
// This function checks the I2C/Timer1 Interrupt Flags
//..............................................................................
void poll_tim1(void){
/*Timer1 zlicza przez ok 13 ms przy FCY 40MHz*/
    TMR1 =0/*clear Timer1*/
    IFS0bits.T1IF=0 ; /*zeruj flage od przepelnienia Timer1*/
    T1CONbits.TCKPS = 0b01 ; /*Prescaler 1:8 , daje nam 5 MHz*/
    T1CONbits.TON=1;    /*start Timer1*/
    /*czekaj na flage MI2C1IF po zakonczeniu poprawnie dowolnej operacji przez Mastera na I2c
      lub na przepelnienie Timer1 czyli ok 13ms*/ 
    while(!(IFS1bits.MI2C1IF | IFS0bits.T1IF ));  
    if(IFS0bits.T1IF){ /*jesli Timer1 przepelniony*/
        IFS0bits.T1IF=0 ; /*zeruj flage od przepelnienia Timer1*/
        IFS1bits.MI2C1IF=0/*clear the I2C general flag*/
        err_flg = 1/*set the error flag*/
        PORTAbits.RA1 = 1 ; /*LED ON*/
        /*tutaj kod uzytkownika do obslugi bledu, np zapalenie diody LED etc*/
    }
    else {err_flg = 0/*clear the error flag*/
        IFS1bits.MI2C1IF=0/*clear the I2C general flag*/
        PORTAbits.RA1 = 0/*LED OFF*/
    }
    T1CONbits.TON=0/*stop Timer1*/
    TMR1 =0/*clear Timer1*/
}
//..............................................................................
// This function generates an I2C Start Condition
//..............................................................................
void i2c_start(void){
while (I2C1STATbits.TRSTAT);        // Wait for bus Idle
I2C1CONbits.SEN = 1        ;        // Generate Start Condition
poll_tim1()                ;    }   // Wait for I2C/Timer1 interrupt flag
//..............................................................................
// This function generates a Restart Condition (for reads)
//..............................................................................
void i2c_restart(void){
while (I2C1STATbits.TRSTAT);        // Wait for bus Idle
I2C1CONbits.RSEN = 1       ;        // Generate a Restart       
poll_tim1()                ;    }   // Wait for I2C/Timer1 interrupt flag
//..............................................................................
// This function generates an I2C Stop Condition
//..............................................................................
void i2c_stop(void){
while (I2C1STATbits.TRSTAT);        // Wait for bus Idle
I2C1CONbits.PEN = 1        ;        // Generate Stop Condition
poll_tim1()                ;    }   // Wait for I2C/Timer1 interrupt flag
//..............................................................................
// Writes a byte to the I2C bus
//..............................................................................
void i2c_wr(unsigned char i2c_data){
while (I2C1STATbits.TRSTAT);        // Wait for bus Idle
I2C1TRN=i2c_data           ;        // Load byte in the transmit buffer
poll_tim1()                ;    }   // Wait for I2C/Timer1 interrupt flag
//..............................................................................
// Reads a byte from the I2C bus
//..............................................................................
unsigned char i2c_rd(void){
while (I2C1STATbits.TRSTAT);        // Wait for bus Idle
I2C1CONbits.RCEN = 1       ;        // Enable Master receive
poll_tim1()                ;        // Wait for I2C/Timer1 interrupt flag
return(I2C1RCV)            ;    }   // Return data in buffer
//..............................................................................
// Generates a Master No Acknowledge on the Bus
//..............................................................................
void i2c_nack(void){
I2C1CONbits.ACKDT = 1      ;        // Set   the related flag for NotAck
I2C1CONbits.ACKEN = 1      ;        // Start Ack sequence
poll_tim1()                ;        // Wait  for I2C/Timer1 interrupt flag
I2C1CONbits.ACKDT = 0      ;    }   // Clear the related flag for ACk
//..............................................................................
// Generates a Master Acknowledge on the Bus
//..............................................................................
void i2c_ack(void){
I2C1CONbits.ACKDT = 0      ;        // Clear  the related flag for Ack
I2C1CONbits.ACKEN = 1      ;        // Start Ack sequence
poll_tim1()                ;    }   // Wait for I2C/Timer1 interrupt flag


/*******************************************************************************
* FileName:        i2c_rtcc.c
* Processor:       PIC24HJ128GP502 
* Hardware:        MCP79410 I2C RTCC
* Compiler:        MPLAB XC16 
*******************************************************************************/
#include "xc.h"
#include "i2c_rtcc.h"
#include "i2c.h"
//******************************************************************************
//                         I2C RTCC DRIVERS 
//******************************************************************************
extern unsigned char err_flg  ;   // the error flag will be used in this file 
//..............................................................................
// The below function writes a data byte in the I2C RTCC
//..............................................................................
void i2c_rtcc_wr(unsigned char rtcc_reg, unsigned char time_var){ // writes a data byte to the I2C RTCC   
i2c_start()             ;  // start I2C communication   
i2c_wr(ADDR_RTCC_WRITE) ;  // write DEVICE ADDR for RTCC WRITES
i2c_wr(rtcc_reg)        ;  // write the register's ADDRESS
i2c_wr(time_var)        ;  // write byte variable in the register 
i2c_stop()           ;  }  // stop I2C communication
//..............................................................................
// The below function reads a data byte from the I2C RTCC
//..............................................................................
unsigned char i2c_rtcc_rd(unsigned char rtcc_reg){ // reads a data byte from the I2C RTCC
unsigned char rtcc_buf  ;  // general data buffer for the i2c rtcc 
i2c_start()             ;  // start I2C communication
i2c_wr(ADDR_RTCC_WRITE) ;  // write DEVICE ADDR for RTCC WRITES
if(err_flg)                // if an error occured at a PICTAIL removal,
return rtcc_buf   ;   }  // leave fast the function 
i2c_wr(rtcc_reg)        ;  // write the register ADDRESS
if(err_flg)                // if an error occured at a PICTAIL removal, 
return rtcc_buf   ;   }  // leave fast the function    
i2c_restart()           ;  // RESTART for READS 
i2c_wr(ADDR_RTCC_READ)  ;  // send the DEVICE ADDRESS for RTCC READS.
if(err_flg)                // if an error occured at a PICTAIL removal,
return rtcc_buf   ;   }  // leave fast the function 
rtcc_buf=i2c_rd()       ;  // read register (stored in 'rtcc_buf')
i2c_nack()              ;  // NOACK from MASTER (last read byte)
i2c_stop()              ;  // stop I2C communication
return rtcc_buf     ;   }  // return the read byte, stored in the general rtcc buffer
//..............................................................................
// The below function initializes the I2C RTCC
//..............................................................................
void ini_i2c_rtcc(void){            // initialization of the I2C RTCC: enables the battery circuit 
                                    // START bit is located in the Sec register
                                    // time/date will be set in 'ini_i2c_time()' 
unsigned char day=0;                // local variable (stores the RTCC DAY register)         
day = i2c_rtcc_rd(ADDR_DAY);        // read day + OSCON bit   
i2c_rtcc_wr(ADDR_DAY,day|VBATEN); } // enable the battery back-up  
//..............................................................................
// The below function initializes the time/date variables, only if the oscillator is not yet running
void ini_i2c_time(void){             // initialization of time/date vars on the I2C RTCC
unsigned char day=0;                 // local variable (stores the RTCC DAY register) 
day = i2c_rtcc_rd(ADDR_DAY);         // read day + OSCON bit 
if((day&OSCON)==OSCON) ;             // if oscillator = already running, do nothing. 
else{                                // if oscillator = not running, set time/date(arbitrary)
i2c_rtcc_wr(ADDR_YEAR,0x17);         // initialize YEAR  register : (20)17           
i2c_rtcc_wr(ADDR_MNTH,0x5);          // initialize MONTH register : maj  
i2c_rtcc_wr(ADDR_DATE,0x28);         // initialize DATE  register : date = 28  
i2c_rtcc_wr(ADDR_HOUR,0x00);         // initialize HOUR  register : hour = 00  
i2c_rtcc_wr(ADDR_MIN,0x00) ;         // initialize MIN   register : min  = 00  
i2c_rtcc_wr(ADDR_SEC,START_32KHZ);}} // init SEC register and start the 32khz oscillator .


/******************************************************************************* 
                              File:   dogm204.c         
 ******************************************************************************/
#include "xc.h" /* wykrywa rodzaj procka i includuje odpowiedni plik*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h> /*dyrektywy uint8_t itp*/
#define FCY 40000000UL /* podajemy wartosc ustawionego zegara (40 MHz), wazne 
aby przed includowaniem <libpic30.h>, potrzebne to jest to wyliczania delay-i*/
#include <libpic30.h> /*biblioteka dajaca dostepp do delay-i.*/
#include "dogm204.h"

/*definicje funkcji*/
void Wyslij_do_LCD(unsigned char bajt)
{
    /*ustaw linie EN, przed wysylka danych*/

    E = 1;
    /*wyslanie 4 najstarszych bitow danych*/
    if(bajt & 0x80) DB7 = 1else DB7 = 0;
    if(bajt & 0x40) DB6 = 1else DB6 = 0;
    if(bajt & 0x20) DB5 = 1else DB5 = 0;
    if(bajt & 0x10) DB4 = 1else DB4 = 0;
    __delay_us(1);
    /*potwierdzenie wyslania danych (opadajacym zboczem EN)*/
    E = 0;
        
    /*ustawienie EN*/
    __delay_us(1);
    E = 1;
    /*wyslanie 4 najmlodszych bitow danych*/    
    if(bajt & 0x08) DB7 = 1else DB7 = 0;
    if(bajt & 0x04) DB6 = 1else DB6 = 0;
    if(bajt & 0x02) DB5 = 1else DB5 = 0;
    if(bajt & 0x01) DB4 = 1else DB4 = 0;
    __delay_us(1);
    /*potwierdz wysylke danych opadajacym zboczem EN*/
    E = 0;

    __delay_us(16);
    
}   


void WlaczLCD()
{
    /*ustawienie kierunku wyjsciowego linii podlaczonych do LCD*/
    TRIS_RESET = 0 ;
    TRIS_RW = 0 ;
    TRIS_RS = 0;
    TRIS_E = 0;
    TRIS_DB7 = 0;
    TRIS_DB6 = 0;
    TRIS_DB5 = 0;
    TRIS_DB4 = 0;

    /*zerowanie linii*/
    RESET = 1 ; /* 0 - Stan aktywny*/
    RW = 0 ;
    RS = 0/* 0 - wskazuje na rejestr rozkazow / 1 - wskazuje na rejestr danych*/
    E = 0;
    DB7 = 0;
    DB6 = 0;
    DB5 = 0;
    DB4 = 0;

    /*Start Inicjalizacji DOGM204 tryb 4-bity*/
    /*zaczekaj co najmniej 5 ms na ustabilizowanie sie napiecia*/
    __delay_ms(5);
    /*Hardware Reset 10ms*/
    RESET = 0 ;
    __delay_ms(10);
    RESET = 1 ;
    __delay_ms(1);
    
  /*Sekwencja startowa dla trybu 4-bit, patrz mini-datasheet str 5*/
  Wyslij_do_LCD(0x33);//wysylamy instrukcje do rejestru rozkazow
  Wyslij_do_LCD(0x32);
  Wyslij_do_LCD(0x2A);
  Wyslij_do_LCD(0x09);
  Wyslij_do_LCD(0x06);
  Wyslij_do_LCD(0x1E);
  Wyslij_do_LCD(0x29);
  Wyslij_do_LCD(0x1B);
  Wyslij_do_LCD(0x6E);
  Wyslij_do_LCD(0x57);
  Wyslij_do_LCD(0x72);
  Wyslij_do_LCD(0x28);
  Wyslij_do_LCD(0x0F); /*Display on, cursor on, blink on*/
  CzyscLCD();
RS = 1 ; /*przelacz na rejestr danych*/  
        
 /*Koniec inicjalizacji i ustawien wyswietlacza DOGM204*/      
}
/*wysyla jeden znak lub stringa*/
void WyswietlLCD(char *napis)
{
    while(*napis){
    Wyslij_do_LCD(*napis++);
    }
         
}

void UstawKursorLCD(uint8_t y, uint8_t x)
{
    uint8_t n ;
    /*y (wiersze) = 1 do 4*/
    /*x (kolumna) = 1 do 20*/
    /*ustal adres pocz?tku znaku w wierszu*/
    switch(y)
    {
        case 1: y = LCD_Line1 ;break;
        case 2: y = LCD_Line2 ;break;
        case 3: y = LCD_Line3 ;break;
        case 4: y = LCD_Line4 ;break;
    
    }
    /*ustal nowy adres pami?ci DD RAM*/
    /*ustaw bajt do Set DDRAM adres*/
    /* x odejmujemy jeden aby przekonwertowa? z 0-19 na 1-20 */
    n = 0b10000000 + y + (x-1) ;
    
    /*wy?lij rozkaz ustawienia nowego adresu DD RAM*/
    RS = 0/*stan niski na lini? RS, wybieramy rejestr instrukcji*/
    Wyslij_do_LCD(n);
    RS = 1;  /*prze??cz na rejestr danych */ 
}

void CzyscLCD()
{
    RS = 0/*przelacz na rejestr rozkazow*/
    Wyslij_do_LCD(1);
    RS = 1/*przelacz na rejestr danych*/
    /*czekaj ??*/
    __delay_us(1);

void WpiszSwojeZnaki(void) {
    /*definicja wlasnych znaków maks 8 szt*/
    char znak1[]= {0,0,14,17,31,16,14,2}; /* definicja literki e z ogonkiem */
    int i; 
    /* adresy poczatku definicji znaku to wielokrotnosc osmiu DEC(0,8,16,24,32,40,48,56)
     * ale uwaga wazne ! adresy kodowane sa na 6 mlodszych bitach dwa najstarsze bity
     * to zawsze  01 (01AAAAAA-gdzie A adres).Uwzgledniajac wartosc calego bajtu
     * adresy poczatku beda wygladal tak HEX(0x40,0x48,0x50,0x58,0x60,0x68,0x70,0x78)
     * Aby wpisac do pamieci wyswietlacza zdefiniowany znak nalezy najpierw wyslac 
     * do rejestru rozkazów (RS na 0) adres poczatku definicji znaku 
     * a w drugim kroku przesylamy dane (RS=1) 8 x bajt (tablica) definjujace obraz znaku*/
    
    RS = 0 ;/*stan niski na linii RS, wybieramy rejestr instrukcji*/
     /*wysylamy instrukcje do rejestru rozkazow (ustaw adres poczatkowy w CGRAM 
      na nasz znak w tym przypadku znak na pozycji drugiej) */
    Wyslij_do_LCD(0x48);/*wysylamy instrukcje do rejestru rozkazow 
     (ustaw adres poczatkowy w CGRAM na nasz znak w tym przypadku znak na pozycji drugiej) */
    
    RS = 1 ;/*stan wysoki na linii RS, wybieramy rejestr danych*/
    /*wysylamy 8 x bajt zdefiniowanego w tablicy znak1[] znaku*/
    for(i=0;i<=7;i++)
    {
       Wyslij_do_LCD(znak1[i]);
    }
   
    RS = 0 ;/*stan niski na lini RS, wybieramy rejestr instrukcji*/
    /*ustawiamy adres DDRAM na pierwszy znak w pierwszej linii, nie zapomnijmy
     o tym poniewaz inaczej zostaniemy w pamieci CGRAM*/
    Wyslij_do_LCD(0x80);
    RS = 1 ; /*stan wysoki na linii RS, wybieramy rejestr danych*/
}


/****************************************************************************** 
                              File:   main.c
 ******************************************************************************/

#include "xc.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h> /*dyrektywy uint8_t itp*/
#include <string.h>
#include "ustaw_zegar.h" /*tutaj m.in ustawione FCY*/
#include <libpic30.h> /*dostep do delay-i,musi byc po zaincludowaniu ustaw_zegar.h*/
#include "i2c.h"
#include "i2c_rtcc.h"
#include "dogm204.h"

/*
 Deklaracje Funkcji 
 */
void ini_INT0(void);
void stop_INT0(void);
void start_INT0(void);
void get_time(void); /*pobiera aktualny czas z rejestrów zegarka*/

/*
 Deklaracje/Definicja Zmiennych 
 */
volatile uint8_t int0_flag=0;   /*flaga zmieniana w przerwaniu i sprawdzana w petli glownej*/
uint8_t sec, min, hr, day, dat, mon, yr ;  /*time/date variables*/  
char czas[20]; /*bufor pomocniczy do konwersji z int na stringa*/                                                   

int main(void) {
ustaw_zegar();/*odpalamy zegar na ok 40MHz*/
ini_i2c1(); /*odpalamy I2C1*/
/* ustawiamy wszystkie piny analogowe (oznacznone ANx) jako cyfrowe
   do zmiany mamy piny AN0-AN5 i AN9-AN12 co daje hex na 16 bitach = 0x1E3F*/
AD1PCFGL = 0x1E3F ;
TRISAbits.TRISA1 = 0 ; /*RA1 jako wyjscie, tu mamy podpieta LED*/
WlaczLCD();            /*inicjalizacja wyswietlacza LCD DOGM204A*/

//ini_i2c_rtcc();   /* init the I2C RTCC device (enable the battery back-up)/dezaktywujemy bo nie uzywamy baterii*/
i2c_rtcc_wr(ADDR_CTRL,SQWE+ALM_NO+MFP_01H); /* square wave on MFP, no alarms, MFP = 1Hz(CONTROL REG)*/
ini_i2c_time() ;   /*init time: 28.05.2017, 00:00:00 AM*/
ini_INT0()     ;   /*initialize INT0: +edge, disable intr, max priority*/
start_INT0()   ;   /*enable INT0*/
while(1)
    {
    
    if ( int0_flag ){  /*co 1 sekunde warunek spelniony*/
        
        get_time(); /*pobierz dane z rejestrów MCP79410 do zmiennych sec,min,hr,dat,mon,yr*/
        UstawKursorLCD(1,1);
        sprintf(czas,"%02i:%02i:%02i",hr,min,sec); /*konwersja z int na char, wynik do bufora czas*/
        WyswietlLCD(czas);/*tu wyswietlamy hr,min,sec na LCD z bufora czas*/
        UstawKursorLCD(2,1);
        sprintf(czas,"%02i-%02i-20%02i",dat,mon,yr); /*konwersja z int na char, wynik do bufora czas*/
        WyswietlLCD(czas);/*tu wyswietlamy dat,mon,yr na LCD z bufora czas*/
        
        int0_flag = 0;    /*zerowanie flagi programowej od przerwania INT1*/ 
        }
                     
    }
    return 0;
}

/*******************************************************************************
                  INTERRUPT SERVICE ROUTINE: EXTERNAL INTR INT0 = MFP
 ******************************************************************************/
 void __attribute__((interrupt,auto_psv))_INT0Interrupt(void){/*INT0 external interrupt generated by MFP(I2C_RTCC)*/
     
    int0_flag = 1;          /*ustawienie flagi programowej w przerwaniu , do odczytu co 1 sek danych z zegara.*/ 
    IFS0bits.INT0IF  =  0 ; /*zeruj flage sprzetowa od INT0*/   
 }      
 /******************************************************************************
                             INIT, START/STOP INTERRUPTS 
  ******************************************************************************/
    void ini_INT0(void)           {   // initialize INT0 
    INTCON2bits.INT0EP = 1        ;   // +edge on INT0 = MFP 
    IFS0bits.INT0IF    = 0        ;   // clear INT0 interrupt flag 
    IEC0bits.INT0IE    = 0        ;   // disable INT0 
    IPC0bits.INT0IP    = 0x0007   ;    }   // max priority for INT0 
 //.............................................................................
    void stop_INT0(void)          {   // disable INT0 external interrupt 
    IEC0bits.INT0IE    = 0   ;    }   // disable INT0
 //.............................................................................
    void start_INT0(void)         {   // enable  INT0 external interrupt
    IEC0bits.INT0IE    = 1   ;    }   // enable  INT0          
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   
  /*****************************************************************************
                    Pobierz czas z rejstrów MCP79410 i skonwertuj z BCD na DEC
   *****************************************************************************/
    void get_time(void) {   
                                       
    yr  = i2c_rtcc_rd(ADDR_YEAR)  ;  /*read YEAR*/        
    yr = (((yr >> 4) & 0x0F) * 10) + (yr & 0x0F); /*konwersja liczby BCD na dziesietna*/              
    
    mon = i2c_rtcc_rd(ADDR_MNTH)  ;  /*read MONTH*/ 
    mon = ((((mon&0x1F) >> 4) & 0x0F) * 10) + (mon & 0x0F); /*konwersja liczby BCD na dziesietna + maska dla leap year*/
       
    dat = i2c_rtcc_rd(ADDR_DATE)  ;  /*read DATE*/ 
    dat = ((((dat&0x3F) >> 4) & 0x0F) * 10) + (dat & 0x0F); /*konwersja liczby BCD na dziesietna*/
        
    hr  = i2c_rtcc_rd(ADDR_HOUR)  ;  /*read HOUR*/
    hr = ((((hr&0x3F) >> 4) & 0x0F) * 10) + (hr & 0x0F); /*konwersja liczby BCD na dziesietna + maska dla 12 hours format*/
       
    min = i2c_rtcc_rd(ADDR_MIN)   ;  /*read MIN*/
    min = (((min >> 4) & 0x0F) * 10) + (min & 0x0F); /*konwersja liczby BCD na dziesietna*/
      
    sec = i2c_rtcc_rd(ADDR_SEC)   ;  /*read SEC*/
    sec = ((((sec&0x7F) >> 4) & 0x0F) * 10) + (sec & 0x0F); /*konwersja liczby BCD na dziesietna + maska dla Start Oscilator*/
    
    }

Linki :

MCP79410 - strona produktu
MCP79410 - datasheet
MCP79410 - rekomendowane kwarce
MCP79410 - zalecane podłączenie
MCP79410 - kompensacja temperatury


Brak komentarzy:

Prześlij komentarz