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.
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.
Pozdrawiam
picmajster.blog@gmail.com
Linki :
MCP79410 - strona produktu
MCP79410 - datasheet
MCP79410 - rekomendowane kwarce
MCP79410 - zalecane podłączenie
MCP79410 - kompensacja temperatury
/*******************************************************************************
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 = 1; else DB7 = 0;
if(bajt & 0x40) DB6 = 1; else DB6 = 0;
if(bajt & 0x20) DB5 = 1; else DB5 = 0;
if(bajt & 0x10) DB4 = 1; else 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 = 1; else DB7 = 0;
if(bajt & 0x04) DB6 = 1; else DB6 = 0;
if(bajt & 0x02) DB5 = 1; else DB5 = 0;
if(bajt & 0x01) DB4 = 1; else 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*/
}
Brak komentarzy:
Prześlij komentarz