niedziela, 20 marca 2011

DIY: Woltomierz na PIC18F2550


Autor: szymonjg
Redakcja: Dondu

Woltomierz pracuje w oparciu o wyprodukowany przez Microchip’a mikrokontroler PIC18F2550.

Jest to popularny i niezbyt drogi układ dorównujący możliwościami Atmelowskiemu Atmega8 może nawet i Atmega16.

Można o nim powiedzieć jeszcze, że wytrzymuje kilkusekundowe odwrotne podłączenie zasilania i jest całkowicie odporny na zwarcie pinów (no może z wyjątkiem przegrzania w skutek wydzielanej mocy) co zostało przeze mnie nie raz już sprawdzone. :-)

Do pomiaru napięcia używam wewnętrznego przetwornika ADC, który umożliwia pomiar z rozdzielczością 10bitów. Otrzymany pomiar jest wyświetlany przy pomocy wyświetlacza 7-segmentowego, oraz linijki LED która w tym przypadku przybrała kształt półokręgu.


Wygląda to tak:


Całość


a działa następująco:






Jak to jest zrobione ...

Urządzenie składa się z kilku w miarę niezależnych modułów połączonych ze sobą taśmami.
Główne moduły to:
  • Procesor
  • Multiplekser diod led
  • Wyświetlacz 7-segmentowy
  • panel z diodami LED
  • przyciski wraz z kontrolkami
  • zasilanie
Całość przybrała formę ładnie wyglądającej walizki ponieważ w przyszłości płytki uniwersalne mają zostać zastąpione porządnymi płytkami drukowanymi. Dodatkowo ma się w nim podobno znaleźć także aktywny filtr pasmowo-przepustowy, ale to już nie na moją głowę. :-)


 Zasilanie.

Całość zasilane jest napięciem 3,3V. Niby nic nadzwyczajnego. Ale ciekawostką jest fakt, że używam jednocześnie dwóch stabilizatorów.


Schemat zasilania.

Na schemacie widzimy już po co są te 2 stabilizatory. Pierwszy to LM317 ustawiony na napięcie ok 7,7V. Będzie ono obniżone przez diodę półprzewodnikową do ok. 7V. Napięcie to będzie ładowało żelowy akumulator 6V. który znajdzie się w walizce jak go kupię.

Dioda jest tu złem koniecznym zapobiegającym cofaniu się prądu z akumulatora na rezystory tworzące dzielnik napięcia dla nóżki ADJ stabilizatora LM317. Bez niej, prąd płynący przez rezystory powoli, lecz nieuchronnie doprowadził by do całkowitego rozładowania akumulatora i w efekcie jego zniszczenia.

Wartości rezystorów ustalamy według wzoru:




Gdzie:
  • Vout  -napięcie wyjściowe
  • Vref  - wewnętrzne napięcie referencyjne stabilizatora
  • R2 - rezystor podciągający nóżkę Adj do masy
  • R1 - rezystor podciągający nóżkę Adj do napięcia wyjściowego
  • Iadj - Prąd nóżki adj.
Napięcie Vout sami sobie ustalamy. Rezystor R1 powinien mieć wartość 240ohm. W praktyce dopuszczalne są rezystory o wartości do 1k.

Parametry Vref i Iadj możemy odczytać z dokumentacji. Typowo są to 1,25V i 50uA. Ale mogą być różne dla różnych regulatorów.

Reszta to już przekształcenie wzoru i obliczenie wartości R2. Na internecie jest w sumie mnóstwo kalkulatorów do obliczania tych wartości ale niektóre nie uwzględniają prądu nóżki ADJ i może się okazać że rzeczywiste napięcie jest wyższe od założonego zwłaszcza przy dużych wartościach opornika R2. Ja sobie po prostu wklepałem ten wzór do arkusza kalkulacyjnego i dobieram oporniki na chybił-trafił. :-)

Kondensatory stabilizują napięcie i sprawiają, że praca stabilizatorów jest jeszcze stabilniejsza. Ich wartości sugeruje nam dokumentacja obu stabilizatorów na schemacie typowej aplikacji.

dokumentacji LM317  mamy:





 A w dokumentacji LM7833:





I jak tu nie czytać dokumentacji kiedy dają nam w nich gotowe schematy...? :-)

O LM317 warto wspomnieć, że jest wewnętrznie zabezpieczony przed przegrzaniem i sam ogranicza sobie płynący przez niego prąd gdy jest mu za gorąco. Dlatego spokojnie można mu dać na wejściu nawet 37V i nie martwić o to, że stabilizator nam się zepsuje w wyniku przegrzania. Ale jeśli damy zbyt mały radiator i LM317 naprawdę zacznie się przegrzewać i ograniczać prąd, no to o stabilnym zasilaniu możemy wtedy zapomnieć.

U mnie LM317 znalazł swoje miejsce na skraju aluminiowej blachy o grubości 1mm. i tyle co do ładowania akumulatora to powinno mu wystarczyć.


Radiator.


W tym przypadku niezbędne stało się jeszcze użycie podkładki i tulejki izolującej, ponieważ stabilizatory te mają różne potencjały na blaszkach stykających się z radiatorem. LM317 ma na blaszce potencjał Vout, a stabilizatory z linii LM78XX chyba masę z tego co pamiętam. Bez odizolowania jednego ze stabilizatorów mielibyśmy zwarcie na radiatorze!

Kondensatory są umieszczone najbliżej nóżek stabilizatora jak to tylko możliwe czyli wszystko zgodnie z dokumentacją. :-)


Procesor

Procesor w moim przypadku znalazł się na płytce uniwersalnej 50x70 mm. Oprócz procesora znalazły się na niej złącza do sterowania modułami, złącza zasilania i kondensatory filtrujące. Zostało trochę miejsca, które użyję na testowanie źródeł referencyjnych. Okazało się potrzebne ponieważ napięcie zasilania, pomimo że stabilizowane, potrafi sobie pływać wraz ze zmianami obciążenia. Próbowałem zrobić proste źródło referencyjne z rezystora i 3-voltowej diody zennera, ale okazało się że charakterystyka diody pozostawiała wiele do życzenia. Przebijała się ona już przy napięciu 2V a wraz ze wzrostem prądu płynącego przez diodę napięcie to dochodziło nawet do 4V i rosło. Oczywiście diodę badałem w kierunku zaporowym.

Płytka wygląda tak.




Jakby ktoś się pytał to drugi ceramik filtrujący masę 8 nogi jest schowany pod procesorem wewnątrz podstawki. Na pin RESETu zalecany jest opornik 10k ale 6k8 też jest dobry. Co prawda reset i tak został wyłączony przeze mnie w bitach konfiguracyjnych, ale zbyt mała wartość rezystancji może spowodować problemy z podaniem na ten pin napięcia 13V i uniemożliwić zaprogramowanie układu. Z tego samego powodu w PICach nie filtruje się kondensatorami, albo filtruje dopiero przez opornik pin RESETu.

Schemat do tej płytki powinien wyglądać mniej-więcej tak:






Dokumentacja mikrokontrolera PIC18F2550

Właściwie nic tu nie ma. Wewnętrzny oscylator ustawiony na 8Mhz daje wystarczającą moc obliczeniową. Dzięki temu procesor nie wymaga zewnętrznego kwarcu. Wszystkim steruje zaledwie 8 linii procesora. Jak to możliwe? O tym za chwilę. Na razie jeszcze wyjaśnię, że nazwy RB6 albo RA4 oznaczają dokładnie to samo co PB6 i PA4. Jakoś tak sobie Microchip wymyślił aby to inaczej nazwać.

Procesory Microchip'a a przynajmniej ten na którym zbudowany jest woltomierz albo nie mają osobnych wejść zasilania dla części analogowej i cyfrowej albo nie są one opisane na pinout'ach. Dlatego cały procesor zasilam poprzez dławik, który powinien odseparować procesor od gwałtownych skoków napięcia zasilania. Wszystkie nóżki zasilania filtruję ceramikami 100n. Samotną nóżkę masy na pinie  8 również sprzęgam kondensatorem z nóżką VDD. Dodatkowo zasilanie całego procesora stabilizuję elektrolitem 220uF.

O tym, że kondensatory naprawdę są potrzebne i dlaczego można poczytać na tym blogu w artykule o Zasilaniu mikrokontrolera i o Dokładności ADC w względem podłączenia.

Multiplekser diod led.

I tu już nam się po części wyjaśni tak małe użycie pinów procesora. Umożliwia mi to masowe sterowanie rejestrów przesuwnych 74HC595. Po przeczytaniu tego artykułu również zacząłem podłączać wszystko na trzech pinach. Zwłaszcza, że zachęca do tego cena rejestrów która oscyluje w granicach złotówki za wersją THT.

 Całość wyszła mi tak:


FRONT_PCB



Ten stabilizator i oporniki przy nim to i tak nie są podłączone. Miało to być coś mądrego, ale nie wyszło. :-)



REAR_PCB

A tak to wygląda z drugiej strony i już w połowie lutowania powiedziałem sobie, że nigdy w życiu więcej!!!

 Siadłem do Eagle'a i stworzyłem coś takiego.




Płytka o wymiarach 8x2cm. Jest nieco bardziej funkcjonalna niż ten wynalazek co mi wyszedł na płytce uniwersalnej. Ale koncepcja dalej pozostała ta sama. Zmieniło się to, że mamy 5 rejestrów w kaskadzie zamiast 3. Daje nam to 40 dwustanowych wyjść pogrupowanych po 10 tak aby można było wpiąć sobie do nich popularną taśmę AWG28 uzbrojoną w wygodne złącza IDC10.

Dodatkowo płytka ma 2 złącza 5-pinowe. Jedno to wejście a drugie to wyjście. Umożliwia nam to łączenie płytek w kaskady i pakowanie piątkami tyle rejestrów na ile nam pozwolą spadki napięcia zasilania. Ilość ścieżek na warstwie TOP została ograniczona a ich ułożenie jest możliwie jak najmniej skomplikowane przez co płytkę można "wyżelazkować" jednostronnie a brakujące ścieżki łatwo zastąpić zworkami czy mostkami.

Schemat płytki:




Płytka została już raz wydrukowana i z powodzeniem zastosowana w innym projekcie, także można brać w ciemno. :-)

W tym woltomierzu mamy mniej więcej coś takiego:





21 diod i 3 rejestry złącze do podłączenia taśmą z płytką mikrokontrolera i tylko... jeden rezystor przy ostatniej diodzie. No nie jest to ładne zachowanie z mojej strony. Wiem. Przepraszam. Tak jakoś wyszło. W zamian za to mamy prostowniczą diodę krzemową podłączoną w kierunku przewodzenia pomiędzy minusami diod a masą. Zadaniem tej diody nie jest bowiem prostowanie prądu bo i tak jest on stały, ale obniżenie napięcia o wartość przebicia diody czyli o ok. 0,7V. A bo sobie wymyśliłem, że zamiast obniżać napięcie dla każdej diody z osobna dwudziestoma rezystorami mogę to zrobić jedną diodą półprzewodnikową.

No bo  jak od napięcia zasilania odejmiemy napięcie złącza PN to da nam:
3,3V-0,7V=2,6V     Takim napięciem można zasilić diodę zielonego koloru której napięcie przebicia wynosi zwykle ok 2,5V. Ostatnia dioda jest wyjątkowa bo ma kolor czerwony, a napięcie świecenia dla czerwonych diod jest znacznie niższe bo ok 1,8-2,2V, dlatego tu rezystor jest już niezbędny.

Jednak wiem, że nie jest zalecane zasilanie diod bez rezystorów i czym to grozi, dlatego przestrzegam:

Nie należy zasilać diod LED źródłem napięciowym bez rezystorów ograniczających ich prąd. W przypadku niewielkiego wzrostu napięcia możemy mieć wtedy znaczny wzrost prądu płynącego przez diodę. Wyjaśnienie Tutaj



Sieć


To zdjęcie przedstawiające połączenie diod może chociaż troszkę usprawiedliwia moje pożałowanie rezystorów.


 Wyświetlacz 7-segmentowy

 Moja perełka chodź jeszcze nad nią pracuję.


7seg


No to już ciężko by było na żelazku zrobić. 21x51mm. 2 strony. Ścieżki 12mil. Płytka robiona typowo do sitodruku w jakiejś firmie. Nawet nie wysilałem się przy niej mocno tylko ustawiłem parametry dla Autorouter'a i przyglądałem się jak mi się ścieżki robią. Koncepcja i nawet pinout złącz taki sam jak w przypadku płytki multipleksera. Płytka zrobiona pod wyświetlacze ze wspólnym plusem.





Schemat raczej prosty. 4 rejestry ułożone w kaskadę (od prawej do lewej) do każdego rejestru przypasowana jest osobna cyfra, co ładnie pasuje bo na 8 wyjść każdego rejestru przypada odpowiednio po 8 segmentów (z przecinkiem) każdej cyfry.

Według mnie jest to najsłuszniejszy sposób sterowania wyświetlaczem 7-segmentowym. Użycie do tego rejestrów z zatrzaskami typu 74hc595 niesie za sobą ogrom zalet:
  • Znowu używamy tylko 3 pinów mikrokontrolera
  • Mamy większą jasność wyświetlaczy ponieważ wszystkie świecą jednocześnie prawie przez 100% swojego czasu.
  • Brak problemu z poświatą (chyba, że celowo ją chcemy zrobić, bo też się da)
  • Najważniejsze: dużo mniejsze zapotrzebowanie mocy obliczeniowej procesora.

Procesora używamy tylko wtedy gdy chcemy zmienić wartość na wyświetlaczu. Nie musimy ciągle multipleksować wyświetlaczami aby oszukać oko. Tu nie ma żadnego oszustwa. Stan wyświetlaczy jest ciągle przechowywany i wyświetlany przez rejestry.

Rozwiązanie to ma jednak jedną podstawową wadę...    Zaś te rezystory.

Wyświetlacze 7-segmentowe zwykle zbudowane są z diod led. Jest to po prostu układ diod ze wspólnym plusem lub minusem ułożonych tak aby dało się z nich wyświetlić cyfrę. Zasady zasilania takiego wyświetlacza są takie same jak zasady zasilania diod. Dlatego w tym układzie każdy segment każdej cyfry powinien być podłączony przez rezystor. Jeśli ktoś nie wie jak dobrać odpowiedni rezystor to jeszcze raz polecam artykuł Dioda Led - Jak obliczyć wartość rezystora?.

Gdybym jednak dołożył do tej płytki 4x8 rezystorów na każdy segment, nawet w jakiejś małej obudowie SMD to mogłoby się okazać, że urosła by ona 2 razy. Dlatego podobnie jak wcześniej ratuję się tu diodami półprzewodnikowymi. Dałem ich 3 w zależności ile kto będzie potrzebował w danym układzie i przy danym napięciu zasilania. Ale to rozwiązanie ma prawo zadziałać jedynie gdy mamy stabilne zasilanie o stałej wartości. W przeciwnym wypadku tylko rezystory.

 Na płytce pożałowałem też kondensatorów filtrujących napięcie zasilania. Od kiedy bawię się rejestrami 74HC595 nigdy nie miałem z nimi problemów z niestabilnym napięciem. Ale dołożenie kilku kondensatorów na pewno by nie zaszkodziło. Ja dałem jeden w obudowie 1206 lutowany niemalże na goldpinach.


Tak to wygląda płytka wyświetlacza - jest niewiele
większa od samych wyświetlaczy.

Problem z nadmiernym prądem diod jest bardzo poważny. Bo właśnie pokazywany tutaj egzemplarz płytki podczas testów przy zapalonych wszystkich segmentach zeżarł mi 1,6A prądu i jeszcze więcej chciał, tylko już mi się go szkoda robiło bo rejestry robiły się ciepłe. :-)

Po sprawy techniczne związane z rejestrami znowu odsyłam do artykułu o LCD HD44780 na 3 pinach i oczywiście do dokumentacji 74hc595 (kopia).

Koncepcję wyświetlania na tym liczb niebawem przedstawię w kodzie.


 Przyciski z kontrolkami.

 Do przestawiania zakresów linijki led służą 4 przyciski podłączone do pinów PORTu B. Przyciski mają zewnętrzne rezystory podciągające do masy. Informacją zwrotną, że przycisk na pewno zadziałał jest zapalenie się diody led podłączonej do tego samego pinu co przycisk. Wszystkim steruje rejestr kierunku który raz ustawia pin jako wejście dla odczytu przycisku a raz jako wyjście dla zapalenia diody.

Schemat podłączenia przycisków.






Tu już nie powinien nikogo kłuć w oczy tylko jeden rezystor dla wszystkich diod led, ponieważ najczęściej i tak świeci tylko jedna, a zapalenie obu albo nawet wszystkich na raz nie spowoduje żadnych zagrożeń dla układu. Dokładniejsze działanie mechanizmu przycisków przedstawiają komentarze do programu.


 Program 

Program pisany jest w środowisku MPlab IDE 8.76. Jest to dedykowane przez Microchipa darmowe IDE. Płatny natomiast jest kompilator MplabC18. Jest co prawda jego darmowa wersja studencka, ale nie zezwala ona na zastosowanie komercyjne. Program podzielony jest na kilka modułów zgodnie z tematyką zawartych w nich funkcji. Kod jest obficie komentowany przez co może być trochę nieczytelny. W załączniku na końcu artykułu jest archiwum z całym projektem bez komentarzy.

 Moduły programu to:
  • main.c
  • plik.h
  • others.c
  • delay.c
  • ADC.c
  • 74hc595.c
  • 7seg.c




Main.c

//Moduł z funkcją main i funkcjami lokalnymi specyficznymi dla tego projektu.
//Funkcje które mogę użyć w innych projektach pakuję do osobnych modułów 
//choćby były one jak najprostsze
//Zamiast pisać od nowa, lepiej dołączyć plik z modułem.

#include "plik.h"
//Dzieląc program na moduły spajam je przy użyciu jednego pliku typu header
//Zwykle w nim dołączam plik deklaracji pinów, deklaracje globalnych słów
//Prototypy funkcji, deklaracje zapowiadające zmiennych globalnych
//i inne elementy które będą obowiązywały we wszystkich modułach gdzie tylko dołączę
//jeden wspólny plik header.


//Konfiguracja bitów konfiguracyjnych
#pragma config PLLDIV = 2, USBDIV = 2                   // CONFIG1L
#pragma config FOSC = INTOSCIO_EC,CPUDIV = OSC1_PLL2//, //FCMEM = OFF, //IESO = OFF                     // CONFIG1H
#pragma config PWRT = OFF, BOR = OFF, VREGEN = ON   //, BORV = 21               // CONFIG2L
#pragma config WDT = OFF, WDTPS = 32768                                     // CONFIG2H
#pragma config MCLRE = ON, LPT1OSC = OFF, PBADEN = OFF, CCP2MX = ON        // CONFIG3H
#pragma config STVREN = ON, LVP = OFF, XINST = OFF, DEBUG = OFF  //, ICPRT = OFF // CONFIG4L
#pragma config CP0 = OFF, CP1 = OFF, CP2 = OFF, CP3 = OFF                   // CONFIG5L
#pragma config CPB = OFF, CPD = OFF                                         // CONFIG5H
#pragma config WRT0 = OFF, WRT1 = OFF, WRT2 = OFF, WRT3 = OFF               // CONFIG6L
#pragma config WRTB = OFF, WRTC = OFF, WRTD = OFF                           // CONFIG6H
#pragma config EBTR0 = OFF, EBTR1 = OFF, EBTR2 = OFF, EBTR3 = OFF           // CONFIG7L
#pragma config EBTRB = OFF  // Function prototypes

//Jeśli używamy kompilatora C18 to co do czego służy mamy w MPlabie klikając Help->Topics...
//Z listy wybieramy PIC18 Config Settings
//A potem w spisie treści wybieramy odpowiedni procesor

//Zamiast pisać to wszystko można w MPLABie użyć Configure->Configuration Bits...
//Ale mając to napisane w kodzie potem łatwiej kopiować konfigurację do nowych programów


#define VRef  3.25   //Kalibracja napięcia referencyjnego.
#define LP 5        //Ilość pomiarów do uśredniania

//Definicja podłączenia klawiszy
#define KEY1  PORTBbits.RB2
#define KEY2  PORTBbits.RB3
#define KEY3  PORTBbits.RB4
#define KEY4  PORTBbits.RB5

//Prototypy funkcji napisanych w tym module
void zapal_diody(unsigned ile);
void graph (double data, unsigned int scale);
void read_key();

double val;  //Zmienna zapisująca wartość zmierzonego napięcia
unsigned int i,position; //licznik pętli i zmienna zapisująca zakres linijki led
unsigned pomiar[LP];     //Tablica zapamiętująca pomiary


//Początek programu
void main(void){

OSCCONbits.IRCF=0b111;    //internal oscilator frequency

all_out();  //Wszystkie porty jako wyjścia i na wszystkich stan 0
init_adc();  //Konfiguracja przetwornika ADC
TRISA=1;     //RA0 jako wejście (do pomiaru ADC)
LATB=0b00111100;  //Przyporządkowanie jedynek pinom pod które podłączone są przyciski
TRISB=0b00011100; //Piny przycisków jako wejścia z wyjątkiem początkowo ustawionego
position=4;       //Wstępne ustawienie skali na największy zakres

while(1){
for(i=0;i<0x4fff;++i) read_key ();     //Czytanie stanu klawiszy + delay
for(i=LP-1;i>0;--i)pomiar[i]=pomiar[i-1]; //przesuwanie poprzednich pomiarów na kolejne miejsca
// Najstarszy pomiar na ostatnim elemencie tablicy jest tracony
//Pierwszy element tablicy jest przygotowany na nowy pomiar 
  pomiar[0]=read_adc((unsigned)0);   //Wykonanie pomiaru

val=pomiar[LP-1];
for(i=LP-1;i>0;--i)val=(val+pomiar[i-1])/2;
  val=val*(VRef/1023);
//Obliczanie średniej. Nie będzie to taka typowa średnia arytmetyczna ale, średnia ważona.
//Najnowszy pomiar ma wagę 50%, poprzedni 25% następny przed nim 12,5% itd.
  

puts_7seg595((val*1000),4,4); //Wysłanie wyniku do wyświetlacza
 graph (val, position);  //Wyświetlenie wyniku na diodach
} 



}



//Funkcja obsługująca klawisze
void read_key()
{LATB=0b0; //Wyłączenie wszystkich diod aby nie przeszkadzały w czytaniu klawiszy.
if (KEY1){ position=1; TRISB=0b00111000;}
if (KEY2){ position=2; TRISB=0b00110100;}
if (KEY3){ position=3; TRISB=0b00101100;}
if (KEY4){ position=4; TRISB=0b00011100;}
//Sprawdzanie stanu klawiszy w celu ustawienia zakresu diod
//Jeśli dany klawisz zostanie wciśnięty to jego pin ustawiany jest jako wyjście.
//aby zapalić podłączoną do niego diodę.
LATB=0b00111100;// Ponowne ustawienie jedynek na pinach przycisków.
//Stan wysoki będzie tylko na tym pinie który jest ustawiony jako wyjście.
//i tylko ta dioda zostanie zapalona.

}


//Konwertowanie wyniku na diody
void graph (double data, unsigned int scale)
{
switch(scale){
case 1: data*=2.0;
case 2: data*=2.0;
case 3: data*=(5.0/4.0);
case 4: data*=4.0;
 }
//Przekonwertowanie napięcia na ilość diod w zależności od zakresu.
//W największym zakresie 4 diody przypadają na 1 volt dlatego
//pomiar w voltach mnożony jest x4. W sumie zakres teoretycznie 
//wynosi 5V a do wyświetlenia mamy 20 diod.
//niższy zakres to 4V dlatego mnożymy dodatkowo pomiar o 1,25. mnożenie
//x4 również się wykona ponieważ po warunku nie ma instrukcji break.
//dla pierwszego pomiaru (1V) wykonają się po kolei wszystkie 4 mnożenia.
//Nie jest to może najoptymalniejszy kod ale za to ładnie wygląda i 
//wyjaśnia dlaczego w instrukcji switch zwykle używamy break;
zapal_diody((unsigned ) data);  
//Po przeliczeniu wyniku zapalamy diody.
// Oczywiście nie zapalimy ułamkowej części diod dlatego rzutujemy na
//typ całkowity. Takiego typu też jest parametr funkcji zapal_diody
//także i tak rzutowanie przebiegło by automatycznie.

}
 

/*-----------------------------------------------------------*/  


//Zapalenie odpowiedniej ilości diod
void zapal_diody(unsigned  ile)
{
unsigned i;
ile=(ile>21?21:ile);
// W sumie mamy "oczko" diod. 20 do wyświetlenia wyniku + 1 do wyświetlenia wyniku pomiaru
//Jeśli w wyniku konwersji wyszło nam więcej diod do zapalenia no to ucinamy wartość do "oczka"

for(i=0;i<=21;++i){
if(i<=(21-ile))puts_register(0,1);
//Warunek zgaszenia diody. Gdybyśmy wcześniej nie ucieli wartości diod a była by ona za duża
//To moglibyśmy porównywać licznik typu unsigned do liczby ujemnej.
else puts_register(1,1);
}
//Ładujemy do rejestrów obsługujących diody 21 bitów. 
//Od najstarszego do najmłodszego bo tak podłączone są diody.
//Najpierw wysyłamy zera które następnie przesuwają się na kolejne pozycje
//rejestru i kolejne rejestry.
//
active_register();
//po załadowaniu rejestru przesuwnego przekazujemy jego wartość do zatrzasków w hc595.
}




plik.h

//Mój własny lokalny plik nagłówkowy którym spajam wszystkie moduły
//Dołączam go do wszystkich modułów z kodem źródłowym


#include <p18f2550.h>//Plik nagłówkowy dzięki któremu możemy korzystać z nazw
// pinów i portów takich samych jak w dokumentacji danego uC

#define FCPU 8000000              // oscillator speed

//Definicja własnych typów
typedef unsigned char BYTE;
typedef unsigned int WORD;
typedef unsigned long int DWORD;

//Prototypy funkcji. Powinny tu się znajdować prototypy wszystkich
//funkcji jakich używamy w programie, jednak kompilator C18 potrafi
//wybaczyć to lenistwo i tylko postraszyć warning'iem.
void delay_ms(int ms);
void delay_s(int s);
void delay_us(int us);

//deklaracja zapowiadająca zmiennej globalnej.
//czasami w pętlach wszystkich modułów używam tego samego licznika
//chodź to może być zgubne.
extern unsigned int i;





others.c

//Moduł na funkcje ogólnego zastosowania

#include "plik.h"

//Funkcja ustawiająca wszystkie piny jako wyjścia i narzucająca im stan 0
//PORTy D i E występują w 40-pinowych PICach18
void all_out(void)
{
 LATA=0;
 LATB=0;
 LATC=0;
// LATD=0;
// LATE=0;

 TRISA=0;
 TRISB=0;
 TRISC=0;
// TRISD=0;
// TRISE=0;
}


//Kolejna funkcja ogólnego zastosowania
//Każdy programista bez względu na język programowania i platformę
//powinien już napisać coś podobnego a nawet i lepszego
// aby w ogóle nazwać się programistą
unsigned  potega(int podstawa,int wykladnik){
unsigned wynik=1;
int i=0;
while(1){
if(i==wykladnik)break;
wynik=wynik*podstawa;
++i;
}
return wynik;
}




delay.c

//Własne delay'e

#include "plik.h"//Plik nagłówkowy


void delay_us(int us)
{unsigned long i=((FCPU/100000000.0)*us)/4;
for (;i>0;--i);
}

void delay_ms(int ms)
{unsigned i;
for (i=0;i<ms;++i)delay_us(998);
}

void delay_s(int s)
{unsigned i;
for (i=0;i<s;++i)delay_ms(1000);
}
//Proste funkcje opuźniające często nie mające nic wspólnego
//zakładanymi czasami
//Mimo wszystko działa mi na nich obsługa wyświetlacza HD44780
//i jakoś nie mam motywacji do napisania porządnych delayów opartych o
//wstawki alemblerowe.

void delay()
 {
   unsigned int i;
   for(i=0;i<100;i++);
}




ADC.c

//Moduł z funkcjami do obsługi ADC


#include "plik.h"//Własny plik nagłówkowy

//Konfiguracja przetwornika analogowo cyfrowego
void init_adc(){
 //Tris na input

//Odpowiednie piny które są wejściami analogowymi trzeba ustawić
// w rejestrze kierunku jako wejścia  i aby o tym nie zapominać to po to mam
//pierwszy komentarz w tej funkcji.

//Wybór napięcia referencyjnego
 ADCON1bits.VCFG=0b00; //Vref=Vdd
//Układanie bitów od najstarszego albo od najmłodszego
 ADCON2bits.ADFM=1;  //A/D Result Format Select bit

//Nie mam pojęcia do czego służą te dwie grupy bitów
//Przypuszczam, że dotyczą one ciągłego samplowania napięcia
//Ale jeszcze tego nie rozgryzłem. 
//Jakoś tak losowo są ustawione i działa.
 ADCON2bits.ACQT=0b001; // A/D Acquisition Time Select bits
 ADCON2bits.ADCS=0b000; //A/D Conversion Clock Select bits

 PIR1bits.ADIF=0;     //Zerowanie flagi przerwania
 IPR1bits.ADIP=0;     //Niski priorytet przerwania
 PIE1bits.ADIE=0;     //Wyłączenie przerwania

 ADCON0bits.ADON=1; //Włączenie konwersji
 ADCON0bits.GO=0; //Zerowanie flagi uruchamiającej konwersję
//informującą o jej zakończeniu.
}



//Odczyt ADC
unsigned int read_adc(int chanel){

 ADCON0bits.CHS=chanel; //wybór kanału do odczytu

 ADCON1bits.PCFG=0xE-chanel; //Ustawienie pinów na analogowe
//I tu jest czasami może być pewien problem wynikający z tabelki
//na stronie 260 w dokumentacji z linku.
//Wynika z niej, że np chcąc skorzystać np. z trzeciego kanału ADC.
//ustawiając tej kanał na analogowy automatycznie analogowymi stają się 
//kanały 2,1,0, których piny mogą być używane w sposób cyfrowy.
//Dlatego najlepiej używać kanałów o małych numerach bo wtedy nie ingerujemy
//niepotrzebnie w inne piny.

 ADCON0bits.GO=1; //Start konwersji napięcia na 10-bitową wartość cyfrową.

 while(ADCON0bits.GO); //Czekamy na zakończenie konwersji
//Bit ADCON0bits.GO zeruje się automatycznie

 ADCON1bits.PCFG=0xF;  //Ustawienie z powrotem wszystkich pinów na cyfrowe
//To właśnie na ewentualność gdybyśmy korzystali z wysokiego kanału i niepotrzebnie
//Przełączali piny które używane są cyfrowo
 return (((unsigned int)ADRESH)<<8)|(ADRESL);
//Zwrócenie wartości. Mamy 10 bitów czyli musimy odczytywać wartość z dwóch bajtów.

}






74hc595.c

//"Biblioteka do obsługi rejestrów 74HC595


#include "plik.h"
//Własny plik header

//Definicje połączeń pinów sterujących rejestrami z pinami uC
//Rejestry multipleksuję linią clock
#define clock0  LATAbits.LATA4
#define clock1  LATAbits.LATA5

#define enable LATCbits.LATC7
#define data   LATCbits.LATC6

//Funkcja wysyłająca bit do rejestrów
//parametr co-wartość bitu
//parametr gdzie-linijka rejestrów do której ma być wysłany 
void puts_register(unsigned co,unsigned gdzie){
//ustalenie wartości na linii data
if(co)data=1;
else data=0;
//tatknięcie odpowiednim zagarem w zależności od wybranego rejestru

switch(gdzie){
case 0: clock0=1; clock0=0; break;
case 1: clock1=1; clock1=0; break;
}
}

//banalna funkcja przekazująca wartości rejestru przesównego w hc595
//do zatrzasków w tych urządzeniach.
//Operację tą wykonuję na raz w rejestrach obsługujących diody i
//reejestrach wyświetlaczy 7segmentowych, ale w tych rejestrach gdzie nie
//wysyłałem nowych danych, czyli nie taktowała ich linnia clock
//to i na wyjściach też nic się nie zmieni.
void active_register(){
enable=1;
enable=0;
}

//funkcja wysyłająca cały bajt, 8bitów od najstarszego do najmłotszego
//parametr bajt- wysyłany bajt. Jego typ to unsigned char co jest 
//zdefiniowane jako typ BYTE w pliku nagłówkowym
//parametr który- wybór linijki rejestrów do której wysłany będzie bajt
void set_register(BYTE bajt, unsigned ktory){
BYTE i;
for(i=0;i<8;++i) 
puts_register(bajt&(1<<7-i),ktory);
}




7seg.c

//Funkcja do obsługi płytki z 7-segmentami


//Znowu zaczynamy od pliku nagłówkowego
#include "plik.h"

BYTE coma=0;  //pozycja przecinka
BYTE minus=0; //minus jeszcze nie obsługiwany

//Fanja funkcja do obsługi mojej płytki z wyświetlaczami
//parametr co-Liczba do wyświetlenia
//parametr coma- pozycja przecinka (0-bez przecinka 1-przecinek za
//ostatnią cyfrą 2-za przedostatnią itd.
//parametr cyfry- na ilu cyfrach wyświetlamy daną liczbę
void puts_7seg595(unsigned int co,unsigned coma,unsigned cyfry){
//BYTE cyfry=0;
unsigned i; //licznik pętli lakalna zmienna zastąpi zmienną globalną
//o tej samej nazwie, której sięga również tutaj

BYTE j; //bufor do wysłania.

for(i=0;i<cyfry;++i){ 
switch((co%potega((unsigned)10,i+(unsigned)1))/potega((unsigned)10,i)){  //wyłuskanie cyfry
case 0: j=0xc0; break;
case 1: j=0xF9; break;
case 2: j=0xa4; break;
case 3: j=0xb0; break;
case 4: j=0x99; break;
case 5: j=0x92; break;
case 6: j=0x82; break;
case 7: j=0xf8; break;
case 8: j=0x80; break;
case 9: j=0x90; break;
//Ustawienie wartości do wysłania tak aby zapaliła nam się odpowiednia cyfra.
}
//Wyświetlacze mają wspólny + dlatego steruje je logiką odwrotną.
//0-sefment zapalony, 1-segment zgaszony.
if(i+1==coma)j&=0x7f;  //Zerowanie bitu zapalającego przecinek
set_register(j,0);
//Wysłanie bajtu do linijki rejestrów obsługujących wyświetlacz.
}
//active_register();
//aktywację zatrzasku w tym programie mogłem zrobić tutaj, ale robię to osobno w kodzie
//funkcji głównej. Czasami do jednego wyświetlacza wysyłamy 2 różne liczby i aktywację
//nie możemy zrobić po każdej z nich bo moglibyśmy nabawić się poświaty.
}


 I na koniec jeszcze parę fotek.


 Interface programowania :-)


Sterownik ze specjalnie przystosowaną taśmą
do podłączenia dwóch kaskad rejestrów.

 Pierwsze testy wyświetlacza



I jeszcze raz efekt końcowy.



Do pobrania

 Na koniec jeszcze udostępniam:

Reszta schematów Eagle'a ma na tyle małą wartość merytoryczną, że szkoda mi czasu na uploadowanie i marnowanie miejsca na nie na serwerze. Każdy ze zdjęć narysuje sobie jeszcze lepsze.

Tyle jak ze mnie.
Mam nadzieję, że komuś się przyda coś z tego co tu napisałem.
Na pytania odpowiem w komentarzach.
Pozdrawiam!

2 komentarze:

  1. na polskim inaczej uczyli;)

    //Proste funkcje opuźniające często nie mające nic wspólnego
    //zakładanymi czasami
    //Mimo wszystko działa mi na nich obsługa wyświetlacza HD44780
    //i jakoś nie mam motywacji do napisania porządnych delayów opartych o
    //wstawki alemblerowe.

    OdpowiedzUsuń
    Odpowiedzi
    1. Yyyyy... No tak.:)

      Nie pamiętam już jak to było bo się z nauczycielką w technikum nie lubiłem i zamiast pisać wypracowania i uczyć się ortografii to wolałem ryć składnię C. :)

      Jak tylko będzie mi dane to poprawię, albo poproszę redakcję o poprawienie literówek, bo i ja znalazłem ich jeszcze trochę a i to pewnie też nie wszystkie.

      Szkoda, że kompilator oprócz błędów w składni kodu nie wytyka też błędów ortograficznych w komentarzach. ;)
      A żeby było jeszcze śmieszniej to cały tekst przed wstawieniem do artykułu wklejałem do Worda aby mi błędy podkreśliło i nie wiem jak ja to przeoczyłem.

      Usuń