środa, 8 lutego 2017

HD44780 na 5 V i PIC24 na 3.3V - łączymy dwa światy napięciowe bez użycia konwerterów.


Wyświetlacz to fajna rzecz nie trzeba chyba przekonywać o tym nikogo. W świecie znakowych wyświetlaczy LCD najpopularniejszym wśród hobbystów  standardem jest wyświetlacz oparty na sterowniku Hitachi HD44780. Tani jak barszcz wolny jak żółw ale frajdy daje co niemiara :) I dla tej frajdy spróbujmy  zasterować ten wyświetlacz za pomocą mikrokontrolera PIC.

W tym mini-projekciku skorzystam z wyświetlacza 4x16 i 16-bitowego mikrokontrolera PIC24HJ128GP502 .

O ile w świecie napięć 3.3V królują wyświetlacze graficzne gdzie nie ma jednego słusznego standardu a różnorodność sterowników może przyprawić o ból głowy o tyle standard HD44780 zadomowił się w świecie 5 V.
Wydaje się , że taki mariaż wyświetlacza +5V i mikrokontrolera zasilanego +3.3 V może nam spowodować jakiś wybuch nuklearny. W praktyce jednak okazuje się, że nie ma z tym żadnego problemu a współpraca układa się idealnie. Oczywiście w takim układzie obawa jest o mikrokontroler zasilany z niższego napięcia ale nawet w przypadku podania +5V na wejście mikrokontrolera (jakimś nie zamierzonym cudem)  nie zjaramy go :) Dlaczego ?? ano dlatego , że wspaniali konstruktorzy PIC-ów przystosowali kilka (7 szt w PIC24HJ128GP502) wejść I/O do przyjęcia na klatę +5V. Ale i tak w naszym mini-projekciku wejścia mikrokontrolera nie będą musiały nadstawiać tej klaty dla +5V.  Jedyną niedogodnością prezentowanego rozwiązania jest konieczność zastosowania dwóch oddzielnych torów zasilania +5V i 3.3V.

W linkach poniżej artykułu jest wyczerpująca ilość informacji technicznych o HD44780.




Opis wyprowadzeń wyświetlacza HD44780 :





  
Przyjmuję następujące założenia do projektu :
  • wyświetlacz HD44780, 4x16 znaków,
  • tryb sterowania (magistrala) - 4 bity,
  • R/W nie wykorzystany (podłączamy do masy) 
  •     ---> RB11
  • RS    --> RB10
  • DB4 --> RB9
  • DB5 --> RB8
  • DB6 --> RB7
  • DB7 --> RB6
  • mikrokontroler PIC 16-bitowy - PIC24HJ128GP502 : zegar wewnętrzny ok.40 MHz. 

Wszystkie zastosowane w projekcie piny mikrokontrolera czyli od RB6 do RB11+5V tolerant, ale jak wspominałem ta właściwość będzie nam zbędna. Wyświetlacz będzie pracował na swoim zasilaniu +5V a mikrokontroler na swoim +3.3V. Sygnały podawane z mikrokontrolera na wyświetlacz będą w logice +3.3 V. Wyświetlacz nie wysyła w stronę mikrokontrolera żadnych poziomów napięciowych.

Po krótce o liniach sterujących E i RS.
Linia E służy do sterowania nazwijmy to drzwiami przez , które wchodzą do wyświetlacza dane (obojętnie czy są to same dane czy rozkazy). Jeśli drzwi są zamknięte czyli E=0 (stan niski) to dane przez zamknięte drzwi nie wejdą. Jeśli natomiast kopniemy w drzwi z buta czyli na E podamy stan wysoki E=1 to drzwi się otworzą i dane obojętnie czy to rozkazy czy nie zostaną przyjęte przez wyświetlacz . Dobrym zwyczajem jest aby drzwi po otwarciu zamknąć, dlatego po każdym bucie E=1 i wysłaniu danych zamykamy drzwi czyli E=0. W rzeczywistości dane zostaną przyjęte na opadającym zboczu sygnały czyli z E=1 na E=0.

Linia RS to taki selektor wyboru (lub inaczej wajha do przełączania toru na lewo i na prawo). W HD44780 mamy dwa główne rejestry (tory) , rejestr rozkazów i rejestr danych. RS=0 przełącza nam wajhę i wskazuje na rejestr rozkazów a RS=1 wskazuje na rejestr danych.

Schemat połączeń :


Na wykresie dioda LED1 nie jest potrzebna do projektu narysowana z rozpędu :)

Ja do budowy projektu użyłem dwóch standardowych płytek stykowych i jednego chińskiego zasilacza do płytek za ok.6 zł :), który ma dwa tory zasilania +5V i +3.3 V co pokrywa nam zapotrzebowanie na zasilanie w projekcie. Masy wewnątrz zasilacza są połączone z dwóch torów. Warto jednak rozejrzeć się za jakimiś nie chińskimi zasilaczami parafrazując przysłowie ludowe tanie zasilacze psy jedzą. Mając porządne zasilanie odpada wiele problemów w uruchamianych projektach.




Podstawową czynnością wstępną jest inicjalizacja wyświetlacza i tutaj najłatwiej jest się wyłożyć w szczególności jak pominiemy jakiegoś wymaganego delay-a. W/g mnie najłatwiej sprawdzić czy wyświetlacz się zainicjalizował poprzez ustawienie w rejestrze rozkazów trybu wyświetlania migającego kursora. Jeśli zobaczymy migający kursor to znaczy , że wyświetlacz jest zainicjalizowany do pracy.
Poniżej oficjalny obrazek na temat inicjalizacji wyświetlacza HD44780.




Pozdrawiam
picmajster.blog@gmail.com


*****************************************LINKI**********************************************

Link z tutorialem o sterowaniu HD44780 bardzo fajny :

Link z LCD Simulatorem :

Link z animacją efektów :

Link z blogiem o HD44780

Artykuł z Elektroniki Praktycznej


Kod programu :
***********************************main.c*****************************************

 1 #include <xc.h> /* wykrywa rodzaj procka i includuje odpowiedni plik nagłówkowy "p24HJ128GP502.h"*/
 2 #include <stdio.h>
 3 #include <stdlib.h>
 4 #include <stdint.h> /*dyrektywy uint8_t itp*/
 5 
 6 #define FCY 40000000UL /* podajemy wartość ustawionego zegara (40 MHz), ważne 
 7 aby przed includowaniem <libpic30.h>, potrzebne to jest do wyliczania delay-i*/
 8 #include <libpic30.h> // biblioteka dająca dostęp do delay-i.
 9 #include "lcd.h"
10 
11 #pragma config JTAGEN = OFF
12 #pragma config FWDTEN = OFF 
13 #pragma config FNOSC = FRC // FOSCSEL-->FNOSC=0b000 (Fast RC Oscillator (FRC))
14 #pragma config FCKSM = CSECMD //FOSC-->FCKSM=0b01 - włącz zegar
15 #pragma config OSCIOFNC = OFF //FOSC-->OSCIOFNC=1 - Fcy będzie na pinie OSCO
16 
17 char napis1[] = "Kocham Monik""\x01"; /* "\x01" to zapis kodu ASCII dla zdefiniowanej
18 literki "ę". Dla własnych znaków mamy zarezerwowane kody ASCII od 0 do 7,
19 przyczym aby używać podanego sposobu wstawiania własnych znaków w stringa nie możemy 
20 używać kodu ASCI 0*/
21 
22 int main(void) {
23  
24       PLLFBD = 41 ; //M=43 (0 bit to 2 stąd 41 = 43 patrz w rejestrze), tutaj 3.685 x 43 = 158.455MHz
25       CLKDIVbits.PLLPRE=0 ;  //N1=2, tutaj 7.37 MHz / 2 = 3.685 MHz
26       CLKDIVbits.PLLPOST=0 ; //N2=2, tutaj 158.455 MHz / 2 = 79.2275 MHz (Fosc)
27    
28        __builtin_write_OSCCONH(0x01); //tutaj argumentem jest wartość z NOSC
29        __builtin_write_OSCCONL(0x01);
30        while(OSCCONbits.COSC !=0b001);
31        while(OSCCONbits.LOCK !=1) {};
32         
33     PMD1bits.AD1MD=1;       //wyłączamy ADC
34     AD1PCFGL = 0x1E3F;      //wyłączenie linii analogowych(wszystkie linie cyfrowe)
35                                         
36     WlaczLCD();             //inicjalizacja wyświetlacza LCD
37     WpiszSwojeZnaki();      //wpisz do CGRAM-u definicję znaku "ę"
38     UstawKursorLCD(2,2);    //wiersz 2, kolumna 2
39     WyswietlLCD(napis1);    //wyświetl napis z tablicy napis1[]
40        
41     while(1)
42       {
43           /*Główna Pętla Programu*/
44    
45       }
46         
47     return 0;
48 }
49 
****************************************lcd.h*************************************
 2 #ifndef LCD_H
 3 #define LCD_H
 4 
 5 /*_TRISB6 --> TRISBbits.TRISB6*/
 6 #define TRIS_RS_LCD   _TRISB11
 7 #define TRIS_EN_LCD   _TRISB10
 8 #define TRIS_DB4_LCD  _TRISB9
 9 #define TRIS_DB5_LCD  _TRISB8
10 #define TRIS_DB6_LCD  _TRISB7
11 #define TRIS_DB7_LCD  _TRISB6
12 /*_RB6 --> PORTBbits.RB6*/
13 #define RS_LCD   _RB11
14 #define EN_LCD   _RB10
15 #define DB4_LCD  _RB9
16 #define DB5_LCD  _RB8
17 #define DB6_LCD  _RB7
18 #define DB7_LCD  _RB6
19 
20 /* przyporządkowanie adresów pamięci DD-RAM do pól wyświetlacza*/
21 /*Uwaga dla wyświetlacza 4x20 [0x00,0x40,0x14,0x54*/
22 #define LCD_Line1 0x00 /*adres 1 znaku 1 wiersza */
23 #define LCD_Line2 0x40 /*adres 1 znaku 2 wiersza */
24 #define LCD_Line3 0x10 /*adres 1 znaku 3 wiersza */
25 #define LCD_Line4 0x50 /*adres 1 znaku 4 wiersza */
26 
27  void Wyslij_do_LCD(unsigned char bajt);
28  void CzyscLCD();
29  void WlaczLCD();
30  void WyswietlLCD(char *napis);
31  void UstawKursorLCD(uint8_t y, uint8_t x);
32  void WpiszSwojeZnaki(void);
33 
34 #endif  /* LCD_H */

***************************************lcd.c*************************************
  1 #include "xc.h" /*wykrywa rodzaj procka i includuje odpowiedni plik nagłówkowy "p24HJ128GP502.h"*/
  3 #include <stdio.h>
  4 #include <stdlib.h>
  5 #include <stdint.h> /*dyrektywy uint8_t itp*/
  6 #include "../string.h"
  7 #define FCY 40000000UL /* podajemy wartość ustawionego zegara (40 MHz), ważne 
  8 aby przed includowaniem <libpic30.h>, potrzebne to jest do wyliczania delay-i*/
  9 #include <libpic30.h> // biblioteka dająca dostęp do delay-i.
 10 #include "lcd.h"
 11 /*deklaracje funkcji*/
 12 void Wyslij_do_LCD(unsigned char bajt);
 13 void CzyscLCD();
 14 void WlaczLCD();
 15 void WyswietlLCD(char *napis);
 16 void UstawKursorLCD(uint8_t y, uint8_t x);
 17 void WpiszSwojeZnaki(void);
 18 /*definicje funkcji*/
 19 void Wyslij_do_LCD(unsigned char bajt)
 20 {
 21         /*ustaw linię EN, przed wysyłką danych*/
 22 
 23     EN_LCD = 1;
 24         /*wysłanie 4 najstarszych bitów danych*/
 25         if(bajt & 0x80) DB7_LCD = 1; else DB7_LCD = 0;
 26         if(bajt & 0x40) DB6_LCD = 1; else DB6_LCD = 0;
 27         if(bajt & 0x20) DB5_LCD = 1; else DB5_LCD = 0;
 28         if(bajt & 0x10) DB4_LCD = 1; else DB4_LCD = 0;
 29         __delay_us(1);
 30         /*potwierdzenie wysłania danych (opadającym zboczem EN)*/
 31         EN_LCD = 0;
 32                 
 33         /*ustawienie EN*/
 34         __delay_us(1);
 35     EN_LCD = 1;
 36         /*wysłanie 4 najmłodszych bitów danych*/        
 37         if(bajt & 0x08) DB7_LCD = 1; else DB7_LCD = 0;
 38         if(bajt & 0x04) DB6_LCD = 1; else DB6_LCD = 0;
 39         if(bajt & 0x02) DB5_LCD = 1; else DB5_LCD = 0;
 40         if(bajt & 0x01) DB4_LCD = 1; else DB4_LCD = 0;
 41         __delay_us(1);
 42         /*potwierdź wysyłkę danych opadającym zboczem EN*/
 43         EN_LCD = 0;
 44 
 45         __delay_us(37);
 46     
 47 }       
 48 
 49 void CzyscLCD()
 50 {
 51         RS_LCD = 0;
 52         Wyslij_do_LCD(1);
 53         RS_LCD = 1;
 54         /*czekaj minimum 1.64 ms*/
 55         __delay_ms(2);
 56 } 
 57 
 58 void WlaczLCD()
 59 {
 60         /*ustawienie kierunku wyjściowego linii podłączonych do LCD*/
 61         TRIS_RS_LCD = 0;
 62         TRIS_EN_LCD = 0;
 63         TRIS_DB7_LCD = 0;
 64         TRIS_DB6_LCD = 0;
 65         TRIS_DB5_LCD = 0;
 66         TRIS_DB4_LCD = 0;
 67 
 68         /*zerowanie linii*/
 69         RS_LCD = 0; /*wskazuje na rejestr rozkazów*/
 70         EN_LCD = 0;
 71         DB7_LCD = 0;
 72         DB6_LCD = 0;
 73         DB5_LCD = 0;
 74         DB4_LCD = 0;
 75 
 76     /*Start Inicjalizacji HD44780*/
 77     /*zaczekaj co najmniej 45 ms na ustabilizowanie się napięcia*/
 78      __delay_ms(45);
 79     
 80     /*powtórz 3 x sekwencję startową 0011 (hex 0x30 i wpisz ją do rejestru rozkazów */
 81     /*(RS=0 ustawione przy zerowaniu linii)*/
 82    
 83     /*ustaw linię EN, przed wysyłką danych*/
 84         EN_LCD = 1;
 85         /*załaduj sekwencję startową 0011*/
 86         DB7_LCD = 0;
 87         DB6_LCD = 0;
 88         DB5_LCD = 1;
 89         DB4_LCD = 1;
 90         
 91         __delay_us(1);
 92         /*potwierdź wysyłkę danych opadającym zboczem EN*/
 93         EN_LCD = 0;
 94         /*zaczekaj co najmniej minimum 4.1 ms*/
 95         __delay_ms(4.1);
 96    
 97          /*ustaw linię EN, przed wysyłką danych*/
 98         EN_LCD = 1;
 99     /*załaduj sekwencję startową 0011*/
100         __delay_us(1);
101         /*potwierdź wysyłkę danych opadającym zboczem EN*/
102         EN_LCD = 0;
103         /*zaczekaj 100 us*/
104         __delay_us(100);
105     
106      /*ustaw linię EN, przed wysyłką danych*/
107         EN_LCD = 1;
108         /*załaduj sekwencję startową 0011*/
109         __delay_us(1);
110         /*potwierdź wysyłkę danych opadającym zboczem EN*/
111         EN_LCD = 0;
112         /*zaczekaj 100 us*/
113         __delay_us(100);
114         
115    /* *********************Koniec Inicjalizacji HD44780***************** */
116     
117    /* ***********************Start ustawień HD44780 ******************** */   
118     /*ustaw parametry wyświetlacza, wysłanie słowa operacyjnego do rejestru rozkazów, RS na 0
119      bit 7 = 0 (musi być 0)
120      bit 6 = 0 (musi być 0)
121      bit 5 = 1 (musi być 1)
122      bit 4 = 0 (słowo danych i interfejs ma 4 bity)
123      bit 3 = 1 (2 wiersze znaków)
124      bit 2 = 0 (matryca 5x8 pikseli)
125      bit 1 = 0 (bez znaczenia)
126      bit 0 = 0 (bez znaczenia) */
127     /*Uwaga po każdym użyciu RS = 0 (wybór rejestru rozkazów), ustawiamy RS = 1*/
128     /*(rejestr danych)*/
129     
130     RS_LCD = 0; /*stan niski na linię RS, wybieramy rejestr instrukcji*/
131     Wyslij_do_LCD(0b00101000);//wysyłamy instrukcję do rejestru rozkazów
132     RS_LCD = 1; /*przełącz na rejestr danych */    
133         
134     /*włącz/wyłącz wyświetlacz, wysłanie słowa operacyjnego, RS na 0
135      bit 7 = 0 (musi być 0)
136      bit 6 = 0 (musi być 0)
137      bit 5 = 0 (musi być 0)
138      bit 4 = 0 (musi być 0)
139      bit 3 = 1 (musi być 1)
140      bit 2 = 1 (wyświetlacz włączony)
141      bit 1 = 0 (kursor wyłaczony)
142      bit 0 = 0 (migotanie kursora wyłączone) */
143         
144     RS_LCD = 0; /*stan niski na linię RS, wybieramy rejestr instrukcji*/
145     Wyslij_do_LCD(0b00001100);//wysyłamy instrukcję do rejestru rozkazów
146     RS_LCD = 1;      /*przełącz na rejestr danych */ 
147     
148     CzyscLCD();
149        
150     /*ustaw tryb pracy wyświetlacza
151      bit 7 = 0 (musi być 0)
152      bit 6 = 0 (musi być 0)
153      bit 5 = 0 (musi być 0)
154      bit 4 = 0 (musi być 0)
155      bit 3 = 0 (musi być 0)
156      bit 2 = 1 (musi być 1)
157      bit 1 = 1 (inkremetacja)
158      bit 0 = 0 (wpis znaku od lewej) */
159     
160     RS_LCD = 0; /*stan niski na linię RS, wybieramy rejestr instrukcji*/
161     Wyslij_do_LCD(0b00000110);/*wysyłamy instrukcję do rejestru rozkazów*/
162     RS_LCD = 1;  /*przełącz na rejestr danych */    
163        
164  /*Koniec inicjalizacji i ustawień wyświetlacza HD44780*/      
165 }
166 
167 void WyswietlLCD(char *napis)
168 {
169     while(*napis){
170     Wyslij_do_LCD(*napis++);
171     }
172          
173 }
174 
175 void UstawKursorLCD(uint8_t y, uint8_t x)
176 {
177         uint8_t n ;
178     /*y (wiersze) = 1 do 4*/
179     /*x (kolumna) = 1 do 16*/
180     /*ustal adres początku znaku w wierszu*/
181         switch(y)
182     {
183         case 1: y = LCD_Line1 ;break;
184         case 2: y = LCD_Line2 ;break;
185         case 3: y = LCD_Line3 ;break;
186         case 4: y = LCD_Line4 ;break;
187     
188     }
189     /*ustal nowy adres pamięci DD RAM*/
190         /*ustaw bajt do Set DDRAM adres*/
191     /* x odejmujemy jeden aby przekonwertować z 0-15 na 1-16 */
192         n = 0b10000000 + y + (x-1) ;
193         
194         /*wyślij rozkaz ustawienia nowego adresu DD RAM*/
195         RS_LCD = 0; /*stan niski na linię RS, wybieramy rejestr instrukcji*/
196         Wyslij_do_LCD(n);
197         RS_LCD = 1;  /*przełącz na rejestr danych */ 
198 }
199 
200 void WpiszSwojeZnaki(void) {
201     /*definicja własnych znaków maks 8 szt*/
202     char znak1[]= {0,0,14,17,31,16,14,2}; /* definicja literki ę */
203     int i; 
204     /* adresy początku definicji znaku to wielokrotność osmiu DEC(0,8,16,24,32,40,48,56)
205      * ale uwaga ważne ! adresy kodowane są na 6 młodszych bitach dwa najstarsze bity
206      * to zawsze  01 (01AAAAAA-gdzie A adres).Uwzględniając wartość całego bajtu
207      * adresy początku będą wyglądać tak HEX(0x40,0x48,0x50,0x58,0x60,0x68,0x70,0x78)
208      * Aby wpisać do pamięci wyświetlacza zdefiniowany znak należy najpierw wysłać 
209      * do rejestru rozkazów (RS na 0) adres początku definicji znaku 
210      * a w drugim kroku wysyłamy 8 x bajt (tablicę) definjujący obraz znaku*/
211     
212     RS_LCD = 0 ;/*stan niski na linię RS, wybieramy rejestr instrukcji*/
213      /*wysyłamy instrukcję do rejestru rozkazów (ustaw adres początkowy w CGRAM 
214       na nasz znak w tym przypadku znak na pozycji drugiej) */
215     Wyslij_do_LCD(0x48);/*wysyłamy instrukcję do rejestru rozkazów 
216      (ustaw adres początkowy w CGRAM na nasz znak w tym przypadku znak na pozycji drugiej) */
217     
218     RS_LCD = 1 ;/*stan wysoki na linię RS, wybieramy rejestr danych*/
219     /*wysyłamy 8 x bajt zdefiniowanego w tablicy znak1[] znaku*/
220     for(i=0;i<=7;i++)
221     {
222        Wyslij_do_LCD(znak1[i]);
223     }
224    
225     RS_LCD = 0 ;/*stan niski na linię RS, wybieramy rejestr instrukcji*/
226     /*ustawiamy adres DDRAM na pierwszy znak w pierwszej linii, nie zapomnijmy
227      o tym ponieważ inaczej zostaniemy w pamięci CGRAM*/
228     Wyslij_do_LCD(0x80);
229 } 

2 komentarze:

  1. Greetings from Idaho! I'm bired at work sso I decided to browse your site on my iphone during lunch
    break. I enjoy the knowledge you provide
    here and can't wait to take a lookk when I get home. I'm shocked at how quick your blog loaded on my phgone ..
    I'm not even using WIFI, just 3G .. Anyhow, wonderful
    site!

    OdpowiedzUsuń
  2. Hello Idaho :) I am glad that your crazy iphone loads my blog so quickly.
    Regards
    PICmajster

    OdpowiedzUsuń