Skocz do zawartości

FPGA (własne programy #6): wyświetlacz LCD do FPGA na Arduino UNO (UART)


FlyingDutch

Pomocna odpowiedź

Cześć,

od dłuższego czasu próbuję rozwiązać problem podłączenia do zestawu FPGA (Elbert i inne zestawy) wyświetlacza LCD. Jest to bardzo wygodne rozwiązanie, ponieważ pozwala na wyświetlaczu alfanumerycznym wyświetlać informacje diagnostyczne, wartości zmiennych, stan układu itp (ogólnie ułatwione debugowanie nowych projektów FPGA). Możemy też wyświetlać wartości sygnałów analogowych np. z przetwornika AD na wejściu układu.

Moje dotychczasowe rozwiązanie polegało na bezpośrednim podłączaniu do układu FPGA prostego wyświetlacza LCD o interfejsie równoległym (zmontowanego na płytce stykowej).

Nie było to jednak rozwiązanie, które mnie satysfakcjonowało z powodu następujących wad:

1) Duża liczba linii danych podpiętych do układu FPGA (co zabierało sporo linii I/O układu FPGA)

2) Potrzeba konwersji poziomów napięć (większość tanich wyświetlaczy LCD ma poziomy napięć 5V) i to dla wielu linii

3) Z powodu wielu linii połączeniowych pomiędzy wyświetlaczem LCD a układem FPGA (do tego dodatkowo połączenia na płytce stykowej) podczas pracy układu zdarzało się, że coś nie kontaktowało i zamiast poświecić czas na projekt FPGA ustalałem dlaczego nie działa

-------------------------------------------------------------

Następnym pomysłem było podłączenie wyświetlaczy o interfejsach SPI i I2C i tu też pojawiły się niedogodności:

1) Dziwne tryby pracy interfejsów na - strona FPGA (np. dla stosunkowo prostej implementacji SPI Master -były opcje:

CPOL = 0 and CPHA = 1 czyli tryb pracy MODE-1, który bardzo trudno ustawić dla Slave będącym wyświetlaczem , nietypowe częstotliwości zegara SPI itp.)

2) Złożoność implementacji I2C i SPI dla układu FPGA i całkiem spora zajętość zasobów : szerokie magistrale IN/OUT równoległe do wprowadzania danych i adresów ze strony FPGA (zajęte dużo lini I/O układu FPGA)

3) Dużo większa trudność i czasochłonność uruchomienia układu wynikająca ze stopnia skomplikowania tych magistral I2C i SPI w porównaniu do np. UART i złożoność implementacji ze strony FPGA (brak dokumentacji lub szczątkowa)

4) Też trzeba często korzystać z konwerterów poziomów dla całkiem sporej liczby linii sygnałów

Co prawda udało mi się podłączyć kilka układów peryferyjnych czujniki, przetwornik AD po SPI i I2Cdo FPGA ale zajęło mi to sporo czasu.

-------------------------------------------------------------------------

Na pomysł rozwiązania wyświetlacza LCD dla zestawu Elbert wpadłem, gdy w szufladzie szukałem jakichś części - w oko wpadł mi shield do Arduino zawierający KeyPad+ wyświetlacz LCD (2x16). Był to shield z firmy Nettigo zakupiony kilka lat temu, zbliżony do aktualnie oferowanych model:

https://nettigo.pl/products/shield-lcd-2x16-z-klawiatura-do-arduino

Postanowiłem, że wyświetlaczem będzie Arduino UNO z tym shieldem, a komunikacja pomiędzy układem FPGA a Arduino UNO będzie oparta o UART (tylko Tx - wysyłanie z FPGA do wyświetlacza na Arduino jednokierunkowo bez potwierdzenia). Do komunikacji UART ze strony układu FPGA użyłem dokładnie tego samego projektu "ISE" co został opisany we wcześniejszym poście:

https://www.forbot.pl/forum/topics51/fpga-wlasne-programy-4-komunikacja-fpga-arduino-uart-rs485-vt14997.htm

Zmieniłem tylko trochę plik ucf : wysyłanie 8-bajtowego komunikatu do wyświetlacza po naciśnięćiu przycisku SW6 (Elbert V2), a linia UART Tx jest wyprowadzona na pinie 1 z Headera 1 (ten najbliżej DIP Switchy po prawej stronie płytki).

Pomiędzy zestawem Elbert V2 a Arduino UNO jest tylko jedna linia połączeniowa: Tx na pinie 1 (Header 1 - Elbert) podpinamy do pinu numer 11 w Arduino UNO (jest to linia Rx w Software Serial na Arduino ). Linię TX w Arduino UNO (pin 10) zostawiamy niepodłączoną.

O ile w układzie komunikacji za pomocą konwerterów UART-RS485 nie musieliśmy stosować konwerterów poziomów logicznych (bo te konwertery miały poziomy 3,3V), to teraz musimy zastosować jedną linię takiego konwertera dla podłaczenia linii Tx (FPGA 3,3V - 5V Arduino UNO). Ja użyłem takiego konwertera poziomów:

https://kamami.pl/konwertery-napiec/234535-dwukierunkowy-konwerter-poziomow-logicznych-18-28v-33-5v.html

Ttaki konwerter dla jednej linii można zrobić na pojedynczym tranzystorze MOSFET. Patrz ten link (tam jest przykładowy schemat):

https://learn.sparkfun.com/tutorials/bi-directional-logic-level-converter-hookup-guide

Ja konwerter poziomów zmontowałem na płytce stykowej, z podwójnym stabilizatorem napięć 3,3V i 5V (układ stabilizatora dla płytek stykowych).

UWAGA: masę napięcia 3,3V 9breadboard) łączymy z pinem GND na Elbercie, a masę 5V z pinem: GND Arduino UNO. Sygnał Tx z FPGA przechodzi przez ten konwerter poziomów i wchodzi na PIN 11 Arduino (RX).

A tak wygląda zmontowany układ:

Tutaj kod części na Arduino UNO (odbiornik UART i wyświetlacz LCD):

#include <LiquidCrystal.h>
#include <SoftwareSerial.h>

LiquidCrystal lcd(8,9,4,5,6,7); //Uwaga: piny SoftSerial nie moga byc zadnym z uzytych w tej linii

SoftwareSerial gtSerial(11, 10); // Arduino RX, Arduino TX (UNO)

byte rx_byte = 0;        // stores received byte
byte serialBuffer[8] = {0,0,0,0,0,0,0,0};
char* komunikat; //Komunikat do wyswietlenia
int count = 0;

void setup() { 
 pinMode(13, OUTPUT);  //we'll use the debug LED to output a heartbeat

 Serial.begin(9600);    // serial / USB port
 gtSerial.begin(9600);  // software serial port

 // set up the LCD's number of columns and rows: 
 lcd.begin(16, 2);
 lcd.print("LCD ready:");
 delay(1000);
 lcd.setCursor(0, 0);  //line=1, x=0
 lcd.print("Komunikat:");  
}

void loop() {
  count = 0;
  // Jesli sa dane na SoftSerial
  while ((gtSerial.available()) && (count < 8)) {
     // get the byte from the software serial port
     rx_byte = gtSerial.read();
     serialBuffer[count] = rx_byte;
     count++;
     delay(10);
  }

 komunikat = (char *) serialBuffer; //cast z byte[8] na tablice znakow
 komunikat[8] = '\0'; //obetnij tablice znakow do 8

 int test = 0;
 for(int i=0; i<8; i++) test += serialBuffer[i];

 //Wyswietlamy jedynie jesli bufor odbiorczy nie pusty
 if (test > 0) {
   Serial.println(komunikat);

   lcd.setCursor(0, 1);  //line=2, x=0
   lcd.print("         ");
   lcd.setCursor(0, 1);  //line=2, x=0   
   lcd.print(komunikat);
 } 
 //Wyczysc bufor - same 0
 for(int i=0; i<8; i++) serialBuffer[i] = 0;

 delay(1000);
 digitalWrite(13, LOW);  
}

To najprostszy kod, który nasłuchuje danych przychodzących po UART (wypełnia bufor odbiorczy o długości 8 bajtów) konwertuje na tablicę znaków i wyświetla na "Serial.monitorze" i wyświetlaczu LCD.

Fajne jest, że ten shield Arduino posiada 5 przycisków, które możemy oprogramować np. użyć do Scroll'a w pionie na wyświetlaczu, resetować display itp. (i to będzie mój kolejny krok z tym projektem).

Jeśli bibliotek:

#include

#include

nie macie z "default'a" zainstalowanych w "Arduino IDE" należy je doinstalować.

W załącznikach projekt "ISE" UART, oraz spakowany program Arduino.

Pozdrawiam

LCDPadSerial.zip

UART_SEND.zip

  • Lubię! 1
Link do komentarza
Share on other sites

Mocno przekombinowane na dłuższą metę, ale ujdzie ^^ I bez takiego Arduino idzie się obyć bez problemu, żeby takim shieldem sterować ^^

Cześć Jakub,

z tym, że przekombinowane to się zgodzę 😉 Wiem, że taki shield można podłączyć bezpośrednio do układu FPGA, ale znów zostaje problem większej ilości linii połączeniowych (i potrzebnych linii konwerterów poziomów - ten shield jest 5-cio woltowy).

Poza tym, weź pod uwagę, że nie jestem tak biegły w FPGA jak Ty - więc " oprogramowanie" połączenia shield'u z układem FPGA (HDL) jest dla mnbie trudniejsze, niż zrobienie tego w Arduino (i zajęłoby mi więcej czasu) .

Pozdrawiam

Link do komentarza
Share on other sites

Zarejestruj się lub zaloguj, aby ukryć tę reklamę.
Zarejestruj się lub zaloguj, aby ukryć tę reklamę.

jlcpcb.jpg

jlcpcb.jpg

Produkcja i montaż PCB - wybierz sprawdzone PCBWay!
   • Darmowe płytki dla studentów i projektów non-profit
   • Tylko 5$ za 10 prototypów PCB w 24 godziny
   • Usługa projektowania PCB na zlecenie
   • Montaż PCB od 30$ + bezpłatna dostawa i szablony
   • Darmowe narzędzie do podglądu plików Gerber
Zobacz również » Film z fabryki PCBWay

Cześć,

zgodnie z zapowiedzią rozszerzyłem program dla Arduino UNO (shield: LCD+ KeyPad) o możliwość poruszanie się "Do góry" i "W dół" po buforze odebranych komunikatów z układu FPGA. Niestety sam bufor wyświetlacza LCD pamiętający otrzymane z FPGA komunikaty z ich numerami ma długość zaledwie stu komunikatów. Definicja bufora w programie:

char DisplayBuffer[100] [12];

bufor zajmuje 100 X 12 bajtów = 1200 bajtów

Wynika to z ograniczenia "Arduino UNO" i jego CPU: dla ATmega328P pojemność pamięci RAM wynosi 2048 bajtów (2KB) , a potrzebna jest jeszcze jej pewna ilość na inne zmienne programu.

Użyłem klawiszy s shield'u LCD (klawisze: góra, dół, select) do scroll'a w górę, dół i resetowania bufora komunikatów i ekranu.

Oto zmodyfikowany program dla "Arduino UNO":

#include <LiquidCrystal.h>
#include <SoftwareSerial.h>

LiquidCrystal lcd(8,9,4,5,6,7); //Uwaga: piny SoftSerial nie moga byc zadnym z uzytych w tej linii

SoftwareSerial gtSerial(11, 10); // Arduino RX, Arduino TX (UNO)

byte rx_byte = 0;        // stores received byte
byte serialBuffer[8] = {0,0,0,0,0,0,0,0};
char* komunikat; //Komunikat do wyswietlenia - char[8]
int count = 0;

//---------------------------------
//Key message
char msgs[5][15] = {"Select Key OK", //Clear buffer
                   "Right Key OK",
                   "Up Key OK    ", //Scroll Up
                   "Down Key OK  ", //Scroll Down
                   "Left Key OK  "};

int  adc_key_val[5] ={20, 270, 520, 690, 880};
int NUM_KEYS = 5;
int adc_key_in;
int key=-1;
int oldkey=-1;

//-------------------------------
int DisplayPointer = 0;
String DisplayPStr = "00";
char DisplayBuffer[100] [12]; //Max. dlugosc UNO RAM = 2048 bytes ograniczenie (stad: max. dlugosc bufora 100 komunikatow)
int WierszPointer = 0;
int WierszPointerCpy = 0;
int DisplayPointerCpy = 0;

void setup() { 
 pinMode(13, OUTPUT);  //we'll use the debug LED to output a heartbeat

 //Inicjalizacja bufora ekranu (wypelnij pustymi komunikatami z numerem linii)
 char emptyMsg[8]  = "        ";
 for(int j=0; j < 100; j++) strcpy(DisplayBuffer[j], FormatujLinieKomunikatu(j, emptyMsg)); 

 Serial.begin(9600);    // serial / USB port
 gtSerial.begin(9600);  // software serial port

 // set up the LCD's number of columns and rows: 
 lcd.begin(16, 2);
 lcd.print("LCD ready:");
 delay(900);
 lcd.setCursor(0, 0);  //line=1, x=0
 lcd.print("            ");  

 DisplayPointer = 0;
 DisplayPStr = "00";
 WierszPointer = 0;

} //end setup

void loop() {  
  // --- Obsluga transmisji SoftSerial ---
  count = 0;
  // Jesli sa dane na SoftSerial
  while ((gtSerial.available()) && (count < 8)) {
     // get the byte from the software serial port
     rx_byte = gtSerial.read();
     serialBuffer[count] = rx_byte;
     count++;
     delay(10);
  }

 komunikat = (char *) serialBuffer; //cast z byte[8] na tablice znakow
 komunikat[8] = '\0'; //obetnij tablice znakow do 8

 int test = 0;
 for(int i=0; i<8; i++) test += serialBuffer[i];

 //Wyswietlamy komunikat na LCD jedynie jesli bufor odbiorczy nie pusty
 if (test > 0) {
   // Najpierw formatowanie wiersza przed zapisem do bufora ekranu
   char* Msg = FormatujLinieKomunikatu(DisplayPointer, komunikat);
   strcpy(DisplayBuffer[DisplayPointer], Msg);

   Serial.println(DisplayBuffer[DisplayPointer]);

   lcd.setCursor(0, WierszPointer);  //line=WierszPointer, x=0
   lcd.print("            ");
   if (WierszPointer == 0) {
      lcd.setCursor(0, 1);  //line=1, x=0
      lcd.print("            ");
   }
   lcd.setCursor(0, WierszPointer);  //line=WierszPointer, x=0   
   lcd.print(DisplayBuffer[DisplayPointer]);

   DisplayPointerCpy = DisplayPointer;
   WierszPointerCpy = WierszPointer;

   // Uaktualnienie wskaznikow ekranu
   if ((++WierszPointer) > 1) WierszPointer = 0;
   if ((++DisplayPointer) > 99) DisplayPointer = 0; // Przechodzimy znow do poczatku bufora ekranu LCD

 } //end if (test > 0)

 //Wyczysc bufor - same 0
 for(int i=0; i<8; i++) serialBuffer[i] = 0;

 // --- Obsluga klawiszy keyPad ---
 adc_key_in = analogRead(0);    // read the value from the sensor 
 key = get_key(adc_key_in);     // convert into key press
 delay(100);

 /*
 Serial.println(key);
 Serial.println(oldkey);
 Serial.println(DisplayPointer);
 delay(100);
 */

 if (key != oldkey)             // if keypress is detected
 {
    delay(50);    // wait for debounce time
    adc_key_in = analogRead(0);    // read the value from the sensor  
    key = get_key(adc_key_in);     // convert into key press

    if (key != oldkey)        
    {     
       if (key >= 0) { 
           oldkey = -1;          
           // Akcja na podstawie naciśnietego klawisza Keypada
           switch (key) {
            case 2:               //Key Up - Scroll Up          
               //Wyczysc LCD             
               lcd.clear();
               if (WierszPointerCpy == 0) {
                  if (--DisplayPointerCpy < 0) DisplayPointer = 0;
                  else DisplayPointer = DisplayPointerCpy;
                  //Wyswietl scrolled buffer
                  lcd.setCursor(0, 0);  //line=1, x=0   
                  lcd.print(DisplayBuffer[DisplayPointer]);
                  lcd.setCursor(0, 1);  //line=2, x=0
                  lcd.print(DisplayBuffer[DisplayPointer+1]);
               } else {
                  // WierszPointerCpy == 1
                  if ((DisplayPointerCpy - 2) < 0) DisplayPointer = 0;
                  else DisplayPointer = DisplayPointerCpy - 2;
                  //Wyswietl scrolled buffer
                  lcd.setCursor(0, 0);  //line=1, x=0   
                  lcd.print(DisplayBuffer[DisplayPointer]);
                  lcd.setCursor(0, 1);  //line=2, x=0
                  lcd.print(DisplayBuffer[DisplayPointer+1]);
               }
               WierszPointer = 1;
               break;
            case 3:               //Key Down  - Scroll Down   
               lcd.clear();
               if (WierszPointerCpy == 0) {
                  if (++DisplayPointerCpy > 99) DisplayPointer = 99;
                  else DisplayPointer = DisplayPointerCpy;
                  //Wyswietl scrolled buffer
                  lcd.setCursor(0, 0);  //line=1, x=0   
                  lcd.print(DisplayBuffer[DisplayPointer]);
                  if (++DisplayPointerCpy > 99) {
                     lcd.setCursor(0, 1);  //line=2, x=0
                     lcd.print("             ");
                     WierszPointer = 0;
                  } else {
                     lcd.setCursor(0, 1);  //line=2, x=0
                     lcd.print(DisplayBuffer[++DisplayPointer]);
                     WierszPointer = 1;
                  }               
               } else {
                  // WierszPointerCpy == 1                                 
                  DisplayPointer = DisplayPointerCpy;
                  //Wyswietl scrolled buffer
                  lcd.setCursor(0, 0);  //line=1, x=0   
                  lcd.print(DisplayBuffer[DisplayPointer]);
                  if (++DisplayPointerCpy > 99) {
                     lcd.setCursor(0, 1);  //line=2, x=0
                     lcd.print("             ");
                     WierszPointer = 0;
                  } else {
                     lcd.setCursor(0, 1);  //line=2, x=0
                     lcd.print(DisplayBuffer[++DisplayPointer]);
                     WierszPointer = 1;
                  }
               }
               break;
            case 0: {              //Key select - Reset LCD screen and LCD Buffer
              //Zerowanie bufora ekranu i ekranu
              lcd.clear();
              key=-1;
              oldkey=-1;
              //-------------------------------
              DisplayPointer = 0;
              DisplayPStr = "00";
              WierszPointer = 0;
              WierszPointerCpy = 0;
              DisplayPointerCpy = 0;

              //Inicjalizacja bufora ekranu (wypelnij pustymi komunikatami z numerem linii)
              char emptyMsg[8]  = "        ";
              for(int j=0; j < 100; j++) strcpy(DisplayBuffer[j], FormatujLinieKomunikatu(j, emptyMsg)); 

              break;
            } //end case 0:
            default:                //Bad Key
              Serial.println("Bad key pressed...");
              break;

          } //end switch

       }//end if (key >=0)
    }//end if (key != oldkey)
 } //end outerif (key != oldkey)

 delay(10);
 digitalWrite(13, LOW);  

} // end loop()

// Convert ADC value to key number
int get_key(unsigned int input)
{
 int k;
 for (k = 0; k < NUM_KEYS; k++)
 {
   if (input < adc_key_val[k])
   {      
      return k;
   }
 }    
   if (k >= NUM_KEYS)
       k = -2;     // No valid key pressed   
   return k;
}

// Funkcja formatuje linie komunikatu do zapisu w buforze ekranu LCD
char* FormatujLinieKomunikatu(unsigned int licznik, char * komunikat) {
   String licznikStr = String(licznik);
   if (licznikStr.length() == 1) licznikStr = "0" + licznikStr;
   String liniaStr = licznikStr + " " + komunikat;
   char Msg[12];
   liniaStr.toCharArray(Msg, 12);
   return Msg;
}

Teraz po otrzymaniu pewnej ilości komunikatów przesłanych z układu FPGA możemy się po nich przemieszczać (w górę i dół), oraz czyścić wszystkie otrzymane komunikaty. Jest to wygodna opcja do "debugowania" układów FPGA na takim prostym wyswietlaczu.

Gdyby ktoś znalazł błędy w programie to proszę o informację (z krótkim opisem jak zreplikować błąd) - postaram się go wtedy usunąć.

Projektu po stronie układu FPGA (Xilinx "ISE") pozostaje bez żadnych zmian.

W załączniku spakowany zmodyfikowany program dla "Arduino UNO".

Pozdrawiam.

LCDPadSerial.zip

  • Lubię! 1
Link do komentarza
Share on other sites

Dołącz do dyskusji, napisz odpowiedź!

Jeśli masz już konto to zaloguj się teraz, aby opublikować wiadomość jako Ty. Możesz też napisać teraz i zarejestrować się później.
Uwaga: wgrywanie zdjęć i załączników dostępne jest po zalogowaniu!

Anonim
Dołącz do dyskusji! Kliknij i zacznij pisać...

×   Wklejony jako tekst z formatowaniem.   Przywróć formatowanie

  Dozwolonych jest tylko 75 emoji.

×   Twój link będzie automatycznie osadzony.   Wyświetlać jako link

×   Twoja poprzednia zawartość została przywrócona.   Wyczyść edytor

×   Nie możesz wkleić zdjęć bezpośrednio. Prześlij lub wstaw obrazy z adresu URL.

×
×
  • Utwórz nowe...

Ważne informacje

Ta strona używa ciasteczek (cookies), dzięki którym może działać lepiej. Więcej na ten temat znajdziesz w Polityce Prywatności.