Zapowiadałem we wpisie o MCP3424, że wezmę się za niego w praktyce. Brakowało mi tylko natchnienia z czym to przetestować. Myślałem na początku , może z popularnym czujnikiem analogowym LM35 ale tutaj zong, czujnik ten działa od 4V czyli elastyki napięciowej nie reprezentuje sobą żadnej.
Problemem z tymi czujnikami jest po za tym uzyskanie temperatur ujemnych, trzeba dodatkowo stosować diody krzemowe w obwodzie. Sięgnąłem zatem w przestworza oferty Microchipa i tutaj miła niespodzianka w postaci MCP9700A.
Nie dość , że MCP9700A jest 2 razy tańszy od LM35 to bije go w kilku punktach, m.in do uzyskania temperatur ujemnych nie trzeba żadnych dodatkowych elementów i nie trzeba angażować dwóch kanałów ADC.
MCP9700A zakupiłem w obudowę TO92 czyli w takiej jak DS18B20.
Garść informacji potrzebnych do działania z MCP9700A (ta literka A jest istotna) :
- zakres mierzonych temperatur -40 do +125 ( w innych obudowach -40 do +150)
- napięcie zasilania od 2,3 do 5.5V
- skok 10mV na 1stC
- 0 stC daje 500mV na wyjściu
- zakres graniczny napięć Vout : 100mV (dla -40stC) do 1,75V (dla +125stC)
Wzorek na napięcie wyjściowe Vout dla czujnika MCP9700A :
np
dla +25 stC Vout wynosi = 10mV x 25 + 500mV = 750mV
dla -40 stC Vout wynosi = 10mV x (-40) + 500mV= 100mV
Jak widzimy napięcie Vout dla MCP9700A wylicza się prosto i przyjemnie.
A poniżej wykres z całym dostępnym zakresem zależności Vout od temperatury.
Przejdźmy zatem do przetwornika MCP3424. Tutaj będzie trochę więcej zabawy z wzorkami.
Najpierw musimy przyjać z jaką rodzielczością ADC będziemy działać. Do wyboru mamy 12 , 14 , 16 , 18 bitów (ustawiane w rejestrze konfiguracyjnym przetwornika) . Z racji tego , że działam na mikrokontrolerze 16-bitowym PIC dlatego cyfra 16 jest dla mnie wyborem optymalnym :)
Musimy wiedzieć o jednej ważnej żeczy dotyczącej przetwornika MCP3424, z racji tego , że przetwornik ten ma możliwość przetwarzania napięć ujemnych i dodatnich równocześnie na kanale ,dlatego ma nóżki oznaczone CH1+ i CH1-. To implikuje fakt, że w próbce ADC musi zmieścić się informacja o znaku. W efekcie tracimy jeden bit, jeśli wybranym trybem ADC jest 16 bitów to w efekcie mam 15 bitów dane (LSB) + 1 bit znak (MSB). Bit znaku jest najstarszym bitem.
Czy jesteśmy z tego powodu bardzo źli ?? ano biorąc pod uwagę , że jednak jest do dyspozycji możliwość określania znaku w mierzonym sygnale i że tracimy na tę opcję 1 bit to ja myślę, że psów na tym przetworniku nie będziemy wieszać.
Na początek zobrazujmy sobie prostym wzorkiem jaką zmianę uzyskamy za pomocą 1 ADC. Czyli krótko mówiąc jeśli z przetwornika odczytamy wartość 1 (ADC) to jaki to będzie reprezentować krok napięcia.
Wzorek :
LSB (wartość danych z przetwornika bez bitu znaku) :
MSB - bit znaku (we wzorku nie będzie ale miejmy pojęcie bo tak w datasheet jest to oznaczane)
LSB = Vref / (2 do potęgi (n-1))
gdzie n wybrana rodzielczość przetwornika, w naszym przypadku 16.
a Vref to napięcie referencyjne do którego przetwornik odnosi swoje działanie w przypadku MCP3424 jest to napięcie 2048 mV (takie jest na pokładzie).
stąd :
LSB = 2048 / (2 do potęgi (16-1)) = 0,0625 V = 62,5 uV.
Taka wartość dla 1 ADC umożliwi nam odczyt temperatury z dokładnością do setnej części, ponieważ schodzimy poniżej 1 mV który reprezentuje w MCP9700A zmianę temperatury o 0,1. Jaki jest sens odczytywać setne części temperatury no nie wiem ale cieszy taka możliwość :)
Warto jeszcze nadmienić, że tamdem MCP9700A + MCP3424 góruje czasem konwersji temperatury nad czujnikim cyfrowym DS18B20. Pamietamy , że w czujniku cyfrowym dla konwersji 12 bitowej potrzeba było aż 750 ms zwłoki.
W naszym przypadku dla 16 bitów czas konwersji to ok 60 ms. Czyli ponad 10 razy szybciej uzyskujemy dane o temperaturze niż w DS18B20.
Tam gdzie krytycznym parametrem jest czas to nasz tandem nie ma sobie równych, no może i ma ale moja wiedza jest na ten temat ograniczona :).
Dobra wracamy do wzorków na podstawie , których chcemy się dogrzebać do wyliczenia temperatury na podstawie odczytanej wartości z przetwornika.
Zatem lećmy po zależnościach jakie nam się uda dostrzec :
Vref = Zakres ADC
Vout = X
gdzie
Vref - 2,048 V (napięcie referencyjne zaszyte w MCP3424)
Zakres ADC- 32767 (15 bitów uwzględniając 1 bit na znak)
Vout - napięcie na wyjściu czujnika MCP9700A
X - ADC odczytane z przetwornika
stąd
X = (Vout x 32767) / 2,048V
a
Vout = (X x 2,048V) / 32767 ***
ufff ta matematyka widać teraz po co trzeba było jej się uczyć :)
z datasheetu znamy inny wzór na Vout mianowicie :
Vout = Tc x Ta + V0stC
gdzie
Tc - współczynnik temperaturowy czyli te 10 mV(0,01V) dającą zmianę o 1 stC
Ta - temperatura mierzona
V0stC - to 500mV(0,5V) dla 0 stC
z powyższego wzoru drogą przekształceń otrzymamy :
Ta = (Vout - 0,5V) / 0,01V
za Vout podstawmy nasze wypociny oznaczone *** powyżej i otrzymujemyw efekcie końcowym drogą przekształceń matematycznych ostateczny wzorek na temperaturę mierzoną :
Ta = (100 x (X x 32767) - 50) = (X x 204.8) / 32767) - 50
Teraz trzeba chwilę zastanowić jak to będzie wyglądało w praktyce. Załóżmy, że wartość odczytana z przetwornika będzie trafiała do zmiennej wynik_ADC. Wtedy aby otrzymać z tej danej temperaturę musimy zrobić trzy kroki :
wynik_ADC = wynik_ADC x 204.8
wynik_ADC = wynik_ADC / 32767
wynik_ADC = wynik_ADC - 50
Powyższe operacje trzeba będzie robić na zmiennych typu float i bardzo dobrze bo po wuja mamy tak mocny procek aby bidolić , że float jest be.
Teraz nadeszła wiekopomna chwila aby zbudować zestaw testowy na razie zmajstrowałem tylko schemat. Warto nadmienić ,że schemat jest zrobiony za pomocą EasyEDA . Polubiłem te bajecznie prostą aplikację webową, Eagle'a to mi się nawet już nie chce odpalać.
Zestaw testowy składać się będzie z :
- mikrokontroler 16 bitowy PIC 24HJ128GP502 (pracujący na 40 MHz z zegarem wewnętrznym czyli bez zewnętrznęgo kwarca)
- MCP9700A w obudowie TO92
- MCP3424 w obudowie SOIC (potrzebujemy płytki konwertera z SOIC14 na DIP14)
- DOGM204A wyświetlacz LCD 4x20 (mój ulubiony m.in z racji mega szybkości, subtelnego wyglądu i pięknego oświetlenia nie walącego po ślipiach)
- zasilacz +3.3V
Sama część sprzętowa nie była przyczynkiem problemów no po zatym , że baran ze mnie i zapomniałem dać na linię I2C rezystorów podciagających. Problem z komunikacją wykryła mi obsługa błędów I2C zaimplementowana przy wykorzystaniu Timera2 i diody LED.
Najważniejsze elementy programu to :
- właściwa sekwencja zapisu do rejestru MCP3424
- właściwa sekwencja odczytu danych z MCP3424
- gimnastyka z konwersją danych celem przekazania ich do wyświetlacza LCD.
Pobieranie danych z przetwornika ustaliłem na 70 ms (1000 / 15(SPS) = 66ms) .
Dane nie potrzebujemy tak szybko wyświetlać dlatego czas odświeżania na LCD przyjąłem sobie na 700ms. Do odmierzania podstawy czasu (10ms) użyłem Timer1 (16 bitowy) a do tykania wewnątrz pętli głównej programu użyłem timera programowego.
Nie zaimplementowałem w programie wykrywanie znaku temperatury ale jest to trywialnie proste, dla trybu 16 bit należy wykryć wskazania ADC poniżej 8000 (jeśli dobrze policzyłem) i wyświetlić wtedy znak "-".
Przetwornik za pomocą odpowiedniego wpisu do rejestru ustawiłem na pracę ciągłą , dane z kanału nr 1, tryb 16 bit (15SPS), wzmocnienie na 1.
Wszystko pięknie działa, przetwornik MCP3424 zdał egzamin w boju. Co do MCP9700A mam jedną uwagę mianowicie wskazania temperatury były ok +1 stC za wysokie czyli czujnik jest optymistą :), można to jednak skorygować w prosty sposób.
Kolejny projekcik zaliczony . Z dużą lekkością robi się te projekciki przy użyciu mikrokontrolerów 16-bitowych PIC. Jest to czysta przyjemność.
Ostatnio zacząłem się rozglądać z ciekawości za 32-bitowcami Microchipa w tym celu zamówiłem potężny i mocny mikrokontroler PIC32MZEF (wydajność aż 330 DMPIS) można by rzec zabójca ARM-ów. Ale ARM-om chwały nie ujmujemy. Czekam na dostawę , najwyżej sobie go na ścianę powieszę :).
Pozdrawiam
picmajster.blog@gmail.com
Linki :
MCP9700A - datasheet
MCP3424 - datasheet
Projekt na GitHub
Pliki projektu :
dogm204.h - plik deklaracji dla wyświetlacza firmy EA model DOGM204-A (4x20)
i2c.h - plik deklaracji dla obsługi i2c
ustaw_zegar.h - plik deklaracji dla obsługi zegara wewnętrznego ok 40 MHz.
dogm204.c - plik definicji dla wyświetlacza firmy EA model DOGM204-A (4x20)
i2c.c - plik definicji dla obsługi i2c
ustaw_zegar.c - plik definicji dla obsługi i konfiguracji zegara wewnętrznego ok 40 MHz.
main.c - główny plik programu
/*****************************************************************************
FileName: ustaw_zegar.h
Processor: PIC24HJ128GP502
Compiler: XC16 ver 1.30
******************************************************************************/
#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 */
/*****************************************************************************
FileName: i2c.h
Processor: PIC24HJ128GP502
Compiler: XC16 ver 1.30
******************************************************************************/
#ifndef I2C_H
#define I2C_H
extern uint8_t error_flag ;
/*deklaracja funkcji*/
void ustaw_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_write(unsigned char i2c_data); /*writes a byte to the I2C bus*/
unsigned char i2c_read(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 obsluga_error(void);
void i2c_write_buf( uint8_t adr_device, uint8_t adr, uint8_t len, uint8_t *buf);
void i2c_read_buf(uint8_t adr_device, uint8_t adr, uint8_t len, uint8_t *buf);
/*dedykowana obsluga EEPROM*/
void EEPROM_sequential_read_buf(uint8_t adr_device, uint16_t subAddr, uint16_t len, char *buf);
void EEPROM_write_buf(uint8_t adr_device, uint16_t subAddr, uint16_t len, char *buf);
void EEPROM_write_buf_page_write(uint8_t adr_device, uint8_t numer_strony, uint8_t numer_komorki, uint16_t len, char *buf);
/*dedykowana obsluga EERAM*/
void EERAM_write_STATUS_REGISTER(uint8_t adr_device, uint16_t subAddr, unsigned char i2c_data);
void EERAM_write_buf(uint8_t adr_device, uint16_t subAddr, uint16_t len, char *buf);
void EERAM_write_byte(uint8_t adr_device, uint16_t subAddr, unsigned char i2c_data);
unsigned char EERAM_read_byte(uint8_t adr_device, uint16_t subAddr);
/*zapis i odczyt struktur*/
void EERAM_write_structure(uint8_t adr_device, uint16_t subAddr, uint16_t len, void *struktura);
void EERAM_sequential_read_structure(uint8_t adr_device, uint16_t subAddr, uint16_t len, void *struktura);
#endif /* I2C_H */
/*****************************************************************************
FileName: dogm204.h
Processor: PIC24HJ128GP502
Compiler: XC16 ver 1.30
******************************************************************************/
#ifndef DOGM204_H
#define DOGM204_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 */
#define CGRAM_SET 0x40
void Wyslij_do_LCD(unsigned char bajt);
void WlaczLCD();
void WyswietlLCD(char *napis);
void lcd_int(uint16_t val);
void UstawKursorLCD(uint8_t y, uint8_t x);
void CzyscLCD();
void WpiszSwojeZnaki(void);
void DefineCharacter(int8_t nr, char *znak);
#endif /* DOGM204_H */
/*****************************************************************************
FileName: ustaw_zegar.c
Processor: PIC24HJ128GP502
Compiler: XC16 ver 1.30
*****************************************************************************/
/*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 st?d 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) {};
}
/*****************************************************************************
FileName: i2c.c
Processor: PIC24HJ128GP502
Compiler: XC16 ver 1.30
******************************************************************************/
#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"
uint8_t error_flag ;
/*definicje funkcji*/
void ustaw_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;
}
void obsluga_error(void){
/*Timer2 zlicza przez ok 13 ms przy FCY 40MHz*/
TMR2 =0; /*clear Timer1*/
IFS0bits.T2IF=0 ; /*zeruj flage od przepelnienia Timer1*/
T2CONbits.TCKPS = 0b01 ; /*Prescaler 1:8 , daje nam 5 MHz*/
T2CONbits.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.T2IF ));
if(IFS0bits.T2IF){ /*jesli Timer1 przepelniony*/
IFS0bits.T2IF=0 ; /*zeruj flage od przepelnienia Timer1*/
IFS1bits.MI2C1IF=0; /*clear the I2C general flag*/
error_flag = 1; /*set the error flag*/
PORTAbits.RA1 = 1 ; /*LED ON*/
/*tutaj kod uzytkownika do obslugi bledu, np zapalenie diody LED etc*/
}
else {error_flag = 0; /*clear the error flag*/
IFS1bits.MI2C1IF=0; /*clear the I2C general flag*/
PORTAbits.RA1 = 0; /*LED OFF*/
}
T2CONbits.TON=0; /*stop Timer1*/
TMR2 =0; /*clear Timer1*/
}
void i2c_start(void){
while(I2C1STATbits.TRSTAT); /*czekaj az linia bedzie wolna */
I2C1CONbits.SEN = 1; /*generuj sygnal start*/
obsluga_error();
}
void i2c_restart(void){
while(I2C1STATbits.TRSTAT); /*czekaj az linia bedzie wolna */
I2C1CONbits.RSEN=1; /*generuj restart*/
obsluga_error();
}
void i2c_stop(void){
while(I2C1STATbits.TRSTAT); /*czekaj az linia bedzie wolna */
I2C1CONbits.PEN=1; /*generuj stop*/
obsluga_error();
}
void i2c_write(unsigned char i2c_data){
while(I2C1STATbits.TRSTAT); /*czekaj az linia bedzie wolna */
I2C1TRN=i2c_data; /*load byte in the transmiter buffer*/
obsluga_error();
}
unsigned char i2c_read(void){
while(I2C1STATbits.TRSTAT); /*czekaj az linia bedzie wolna */
I2C1CONbits.RCEN=1; /*enable Master receive*/
obsluga_error();
return(I2C1RCV); /*return data in buffer*/
}
void i2c_ack(void){
I2C1CONbits.ACKDT=0; /*clear the related flag for ACK*/
I2C1CONbits.ACKEN=1; /*start ACK sequence*/
obsluga_error();
}
void i2c_nack(void){
I2C1CONbits.ACKDT=1; /*set the related flag for NotACK*/
I2C1CONbits.ACKEN=1; /*start ACK sequence*/
obsluga_error();
I2C1CONbits.ACKDT=0; /*clear the related flag for ACK*/
}
void i2c_write_buf( uint8_t adr_device, uint8_t adr, uint8_t len, uint8_t *buf ) {
i2c_start();
i2c_write(adr_device); /*wyslanie adresu urzadzenia z bitem R/W w stanie niskim*/
i2c_write(adr);
while (len--) i2c_write(*buf++);
i2c_stop();
}
void i2c_read_buf(uint8_t adr_device, uint8_t adr, uint8_t len, uint8_t *buf) {
i2c_start();
i2c_write(adr_device);/*wyslanie adresu urzadzenia z bitem R/W w stanie niskim*/
i2c_write(adr);
i2c_start();
i2c_write(adr_device + 1);/*zapisuje adres urzadzenia z bitem R/W ustawionym na 1 czyli o 1 zwiekszamy adres urzadzenia*/
while (len--){
if(len) {
*buf++ = i2c_read();
i2c_ack();
}
else {
*buf++ = i2c_read();
i2c_nack();
}
}
i2c_stop();
}
/*Funkcje dedykowane do obslugi pamieci EEPROM PIC 24LCxxx*/
/*odczyt sekwencyjny danych z pamieci EEPROM.*/
void EEPROM_sequential_read_buf(uint8_t adr_device, uint16_t subAddr, uint16_t len, char *buf) {
i2c_start();
i2c_write(adr_device); /*wyslanie adresu urzadzenia z bitem R/W w stanie niskim*/
i2c_write((subAddr & 0xFF00) >> 8) ; /*wyslanie starszego bajtu adresu pamieci*/
i2c_write(subAddr & 0x00FF) ; /*wyslanie mlodszego bajtu adresu pamieci*/
i2c_start();
i2c_write(adr_device + 1);/*zapisuje adres urzadzenia z bitem R/W ustawionym na 1 czyli o 1 zwiekszamy adres urzadzenia*/
i2c_ack();
while(len--)
{
*buf++ = i2c_read();
i2c_ack();
}
i2c_nack();
i2c_stop();
}
/*zapis danych do pamieci EEPROM w pojedynczych bajtach*/
void EEPROM_write_buf(uint8_t adr_device, uint16_t subAddr, uint16_t len, char *buf) {
while (len--) {
i2c_start();
i2c_write(adr_device); /*wyslanie bajtu adresu z bitem R/W ustawionym na zero*/
i2c_write((subAddr & 0xFF00) >> 8); /*wyslanie starszego bajtu adresu pamieci*/
i2c_write(subAddr & 0x00FF); /*wyslanie mlodszego bajtu adresu pamieci*/
i2c_write(*buf++);
i2c_stop();
__delay_ms(5); /*oczekiwanie na zapis*/
subAddr++;
}
}
/*zapis danych do pamieci EEPROM w trybie PAGE WRITE czyli maksymalnie 128 bajty w obrembie jednej strony*/
void EEPROM_write_buf_page_write(uint8_t adr_device, uint8_t numer_strony, uint8_t numer_komorki, uint16_t len, char *buf) {
uint16_t subAddr;
/*subAddr sklada sie z :
numer komorki to numer w obrebie jednej strony czyli od 0 do 127
numer strony to numer jednej z 512 stron od 0 do 511
24LC512 ma 512 stron 128 bajtowych co lacznie daje 65536 bajtow*/
i2c_start();
i2c_write(adr_device); /*wyslanie bajtu adresu Slave z bitem R/W ustawionym na zero*/
/*wyliczamy adres poczatku uwzgledniajac numer strony 0-511 i numer komorki na stronie 0-127*/
subAddr = (numer_strony*128) + numer_komorki;
i2c_write((subAddr & 0xFF00) >> 8); /*wyslanie starszego bajtu adresu pamieci*/
i2c_write(subAddr & 0x00FF); /*wyslanie mlodszego bajtu adresu pamieci*/
while (len--) {
/*inkrementujemy tylko tablice z danymi,adres w obrebie strony 0-127 bajtow jest inkrementowany automatycznie przez eeprom*/
i2c_write(*buf++);
}
i2c_stop();
__delay_ms(5); /*oczekiwanie na zapis*/
}
/*Funkcje dedykowane do obslugi pamieci EERAM PIC 47L04 lub 47L16*/
/*zapis danych do rejestru pamieci EERAM*/
void EERAM_write_STATUS_REGISTER(uint8_t adr_device, uint16_t subAddr, unsigned char i2c_data) {
i2c_start();
i2c_write(adr_device); /*wyslanie bajtu adresu z bitem R/W ustawionym na zero*/
i2c_write((subAddr & 0xFF00) >> 8); /*wyslanie starszego bajtu adresu pamieci*/
i2c_write(subAddr & 0x00FF); /*wyslanie mlodszego bajtu adresu pamieci*/
i2c_write(i2c_data);
i2c_stop();
__delay_ms(1); /*oczekiwanie na zapis do STATUS REGISTER*/
}
/*zapis danych do pamieci EERAM - tryb sekwencyjny czyli ciag bajtow*/
void EERAM_write_buf(uint8_t adr_device, uint16_t subAddr, uint16_t len, char *buf) {
i2c_start();
i2c_write(adr_device); /*wyslanie bajtu adresu z bitem R/W ustawionym na zero*/
i2c_write((subAddr & 0xFF00) >> 8); /*wyslanie starszego bajtu adresu pamieci*/
i2c_write(subAddr & 0x00FF); /*wyslanie mlodszego bajtu adresu pamieci*/
while (len--) {
/*inkrementujemy tylko dane,adres jest inkrementowany automatycznie przez EERAM*/
i2c_write(*buf++);
}
i2c_stop();
}
/*zapis danych do pamieci EERAM - tryb pojedynczego bajtu*/
void EERAM_write_byte(uint8_t adr_device, uint16_t subAddr, unsigned char i2c_data) {
i2c_start();
i2c_write(adr_device); /*wyslanie bajtu adresu z bitem R/W ustawionym na zero*/
i2c_write((subAddr & 0xFF00) >> 8); /*wyslanie starszego bajtu adresu pamieci*/
i2c_write(subAddr & 0x00FF); /*wyslanie mlodszego bajtu adresu pamieci*/
i2c_write(i2c_data);
i2c_stop();
}
/*odczyt jednego bajtu z pamieci EERAM.*/
unsigned char EERAM_read_byte(uint8_t adr_device, uint16_t subAddr) {
unsigned char i2c_data;
i2c_start();
i2c_write(adr_device); /*wyslanie adresu urzadzenia z bitem R/W w stanie niskim*/
i2c_write((subAddr & 0xFF00) >> 8) ; /*wyslanie starszego bajtu adresu pamieci*/
i2c_write(subAddr & 0x00FF) ; /*wyslanie mlodszego bajtu adresu pamieci*/
i2c_start();
i2c_write(adr_device + 1);/*zapisuje adres urzadzenia z bitem R/W ustawionym na 1 czyli o 1 zwiekszamy adres urzadzenia*/
i2c_ack();
i2c_data = i2c_read();
i2c_nack();
i2c_stop();
return(i2c_data);
}
/*funkcje do zapisu i odczytu struktur*/
/*zapis danych do pamieci EERAM - tryb sekwencyjny czyli ciag bajtow*/
void EERAM_write_structure(uint8_t adr_device, uint16_t subAddr, uint16_t len, void *struktura) {
/*tworzymy wskaznik ktory wskazuje na pierwszy element struktury zrzutowanej do typu uint8_t*/
uint8_t *wsk = (uint8_t*)struktura;
i2c_start();
i2c_write(adr_device); /*wyslanie bajtu adresu z bitem R/W ustawionym na zero*/
i2c_write((subAddr & 0xFF00) >> 8); /*wyslanie starszego bajtu adresu pamieci*/
i2c_write(subAddr & 0x00FF); /*wyslanie mlodszego bajtu adresu pamieci*/
while (len--) {
/*inkrementujemy tylko dane,adres jest inkrementowany automatycznie przez EERAM*/
i2c_write(*wsk++);
}
i2c_stop();
}
/*odczyt sekwencyjny danych z pamieci EERAM.*/
void EERAM_sequential_read_structure(uint8_t adr_device, uint16_t subAddr, uint16_t len, void *struktura) {
/*tworzymy wskaznik ktory wskazuje na pierwszy element struktury zrzutowanej do typu uint8_t*/
uint8_t *wsk = (uint8_t*)struktura;
i2c_start();
i2c_write(adr_device); /*wyslanie adresu urzadzenia z bitem R/W w stanie niskim*/
i2c_write((subAddr & 0xFF00) >> 8) ; /*wyslanie starszego bajtu adresu pamieci*/
i2c_write(subAddr & 0x00FF) ; /*wyslanie mlodszego bajtu adresu pamieci*/
i2c_start();
i2c_write(adr_device + 1);/*zapisuje adres urzadzenia z bitem R/W ustawionym na 1 czyli o 1 zwiekszamy adres urzadzenia*/
i2c_ack();
while(len--) {
/*inkrementujemy tylko dane,adres jest inkrementowany automatycznie przez EERAM*/
*wsk++ = i2c_read();
i2c_ack();
}
i2c_nack();
i2c_stop();
}
/*****************************************************************************
FileName: dogm204.c
Processor: PIC24HJ128GP502
Compiler: XC16 ver 1.30
******************************************************************************/
#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*/
}
void WyswietlLCD(char *napis)
{
while(*napis){
Wyslij_do_LCD(*napis++);
}
}
// wyslanie liczby dziesietnej
void lcd_int(uint16_t val)
{
char bufor[17];
sprintf(bufor,"%i",val);
WyswietlLCD(bufor);
}
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);
}
/*funkcja definiuje jeden znak*/
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 */
char znak1[]= {0x0C,0x12,0x12,0x0C,0,0,0,0};/*definicja stC*/
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*/
}
/*funkcja uniwersalna do definiowania znaku*/
void DefineCharacter(int8_t nr, char *znak)/*nr 0...7, *znak to wskaznik na tablice z danymi*/
{
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*/
Wyslij_do_LCD((nr*8)|CGRAM_SET);/*ustaw adres poczatkowy w CGRAM na nasz znak*/
RS = 1 ;/*stan wysoki na linii RS, wybieramy rejestr danych*/
/*wysylamy 8 x bajt zdefiniowanego w tablicy znak[] znaku*/
for(i=0;i<=7;i++)
{
Wyslij_do_LCD(*znak++);
}
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*/
/*definiowanie znaku DefineCharacter(1,tablica) gdzie nr 0...7 a tablica to
np char tablica[]= {0x0C,0x12,0x12,0x0C,0,0,0,0} definicja stC*/
/*wywolanie zdefiniowanego znaku Wyslij_do_LCD(nr) gdzie nr to 0...7 lub
WyswietlLCD("\nr"*/
}
/*****************************************************************************
FileName: main.c
Processor: PIC24HJ128GP502
Compiler: XC16 ver 1.30
******************************************************************************/
#include "xc.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h> /*dyrektywy uint8_t itp*/
#include <string.h>
#include <math.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 "dogm204.h"
#include "i2c.h"
/*******************************************************************************
DEKLARACJE FUNKCJI
******************************************************************************/
void Set_MCP3424(void);
int read_MCP3424(void);
void InitTimer1(void);
/*******************************************************************************
DEKLARACJE/DEFINICJE ZMIENNYCH
******************************************************************************/
#define AdressMCP3424 0x0 /*A0,A1 --> 00*/
volatile uint8_t Timer1_Programowy , Timer2_Programowy ;
float wynik, wynikTotal, wynikFraction;
int wynikADC;
char tablica[16];
int licznik;
int main(void) {
ustaw_zegar(); /*odpalamy zegar wewnetrzny na ok 40MHz (39.61375 MHz)*/
__delay_ms(50) ; /*stabilizacja napiec*/
/*
* wylaczamy ADC , wszystkie piny chcemy miec cyfrowe
* pojedynczo piny analogowe wylaczamy w rejestrze AD1PCFGL
* Po resecie procka piny oznaczone ANx sa w trybie analogowych wejsc.
*/
PMD1bits.AD1MD=1 ; /*wylaczamy ADC*/
/*
* 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*/
InitTimer1(); /*Timer 1 Init*/
ustaw_I2C1(); /*I2C1 Init*/
WlaczLCD(); /*LCD Init*/
WpiszSwojeZnaki(); /*znak stopnia definjujemy*/
Set_MCP3424(); /*konfiguracja przetwornika*/
licznik = 0;
while (1) {
/*w trybie 16 bitow mamy 1 probke po ok 66 ms (15SPS) (1000ms / 15) odczyt zrobimy co 70ms*/
/*co 70ms wykonaj czynnosc*/
if(!Timer1_Programowy) {
Timer1_Programowy = 7 ; /*Timer1 sprzetowy x Timer1_Programowy = 10ms x 7 = 70ms*/
if(licznik > 10) licznik = 0; /*10 x 70ms = 700ms*/
wynikADC = read_MCP3424();/*pobieramy dane do zmiennej celem dalszych przeksztalcen*/
/*tutaj wstawi? trzeba jeszcze wykrywanie znaku temp. na podstawie ADC, 0st to 7999,75 dla tryby 16bit*/
wynik = wynikADC;
wynik *= 204.8 ;
wynik /= 32767.0 ;
wynik -= 50.0 ;
/*rozdziel wynik na czesc calkowita (wynikTotal) i ulamkowa (wynikFraction), przygotuj do wyswietlania na LCD*/
wynikFraction = modff(wynik, &wynikTotal);/*operacja rodzielania na czesc calkowita i ulamkowa*/
wynikFraction *= 10.0; /*interesuje nas 1 cyfra czesci ulamkowej, przenosimy ja na czesc calkowita*/
wynikFraction = floorf(wynikFraction); /*wyodrebnij przeniesiona z czesci ulamkowej czesc calkowita*/
wynikADC = (int)wynikTotal;
sprintf(tablica,"%i",wynikADC); /*zmienna int konwertujemy na char i umieszczamy ja w tablicy*/
/*dane mamy gotowe do wyswietlania*/
/*wyswietlamy dane co ok 700ms, nie ma potrzeby szybciej*/
if(++licznik == 10) {
UstawKursorLCD(1,1);
WyswietlLCD("Temperatura: ");
WyswietlLCD(tablica); /*wyswietlamy czesc calkowita temperatury*/
Wyslij_do_LCD(44); /*przecinek*/
wynikADC = (int)wynikFraction; /*czesc ulamkowa (1 cyfra)*/
sprintf(tablica,"%i",wynikADC);/*alternatywa dla znanej z Atmeg funkcji itoa, konwertuj z int na char*/
WyswietlLCD(tablica); /*wyswietlamy czesc ulamkowa temperatury*/
WyswietlLCD("\x01""C"); /*wyswietlamy zdefiniowany znak stC, po \ podajemy kod ASCII nowego znaku*/
}
}
}
return 0;
}
void Set_MCP3424(void)
{
i2c_start();
/*MCP3421 ADDRESS BYTE - MSB first*/
/* 1 - 1 - 0 - 1 - A2 - A1 - A0 - R/W*/
i2c_write(0b11010000); /*wysylamy bajt z adresem MCP3424 R/W - 0*/
__delay_us(10); /*zwloka na ustawienie ACK przez MCP3424*/
/*MCP3421 CONFIGURATION REGISTER - MSB first*/
/*RDY - C1 - C0 - O/C - S1 - S0 - G1 - G0*/
/*RDY - ready bit,
C1-C0 - channel selection bit
O/C - conversion mode bit
S1-S0 - sample rate
G1-G0 - PGA gain selection*/
i2c_write(0b00011000); /*wysylamy bajt konfiguracyjny Continuous conversion - 16bit - channel 0 - PGA = 1*/
__delay_us(10); /*zwloka na ustawienie ACK przez MCP3424*/
i2c_stop();
}
/*funkcja odczytu danych z MCP3424*/
int read_MCP3424(void)
{
uint16_t readADC=0;
i2c_start();
i2c_write(0b11010001);/*wysylamy bajt z adresem MCP3424 R/W - 1*/
__delay_us(10); /*zwloka na ustawienie ACK przez MCP3424*/
readADC=i2c_read(); /*odczytujemy starszy bajt z danymi*/
i2c_ack();
readADC<<=8; /*przesuwamy odebrany bajt na pozycje starszego bajtu w slowie 16 bitowym*/
readADC+=i2c_read(); /*odczytujemy mlodszy bajt z danymi i sumujemy go z poprzednia wartoscia*/
/*konczymy odczyt, pomijamy odczyt bajtu konfiguracyjnego*/
i2c_nack();
i2c_stop();
return(readADC);
}
/*Inicjacja/konfiguracja Timera1*/
void InitTimer1(void){
/*konfiguracja Timer1*/
T1CONbits.TON = 0 ; /*Stop the timer1*/
TMR1 = 0x0000 ;/*Clear timer register*/
/*Zegar ok 40 MHz(39.61375 MHz) Prescaler 1:64; PR1 Preload = 61897; Actual Interrupt Time = 100 ms*/
T1CONbits.TCKPS = 0b10 ; /*Prescaler 1:64*/
/*konfiguracja przerwania*/
IFS0bits.T1IF = 0; /*Clear Timer1 Interrupt Flag*/
IEC0bits.T1IE = 1; /*Enable Timer1 Interrupt*/
/*ustaw priorytet dla przerwania*/
IPC0bits.T1IP = 0b100 ; /*Set Timer1 Interrupt Prioryty Level 4*/
/*TMR1 zlicza do wartosci ustawionej w PR1 jesli TMR1=PR1 zglaszane jest przerwanie*/
/*40MHz / Prescaler / PR1*/
/*ok.10Hz (100ms*/
//PR1 = 61897; /*Load period value*/
//T1CONbits.TON = 1 ; /*Start the timer1*/
/*ok.100Hz (10ms)*/
PR1 = 6189; /*Load period value*/
T1CONbits.TON = 1 ; /*Start the timer1*/
}
/*Obsluga wektora przerwania dla Timer1*/
void __attribute__((interrupt, no_auto_psv))_T1Interrupt(void)
{
/*kod uzytkownika do obslugi przerwania*/
uint8_t x;
x = Timer1_Programowy ;
if (x) Timer1_Programowy = --x ;
x = Timer2_Programowy ;
if (x) Timer2_Programowy = --x ;
/* Clear Timer1 interrupt */
IFS0bits.T1IF = 0 ; /*Clear Timer1 Interrupt Flag*/
}
Brak komentarzy:
Prześlij komentarz