Popularny post ethanak Czerwiec 20, 2023 Popularny post Udostępnij Czerwiec 20, 2023 Ta treść została wynagrodzona przez moderatora! ethanak otrzymał odznakę: "Za udzielenie wzorowej odpowiedzi" 12 godzin temu, prezesedi napisał: Przepraszam W sumie nie ma za co - namieszali producenci modułów stosując różne oznaczenia 🙂 Ale przejdźmy do tematu. Zacznę może od pewnego ustalenia: ponieważ różne moduły PCF8575 mają różne oznaczenia pinów IO, będę posługiwał się numeracją zgodną z oficjaną dokumentacjhą układu - czyli P0..P7 (młodszy bajt) i P10..P17 (starszy bajt). No, ale przejdźmy do konkretów. Uznałem że nazwa parametru "shift" niespecjalnie oddaje jego znaczenie. Zmiana nazwy na "whereto" wydała mi się bardziej odpowiednia, szczególnie że w przyszłości będę mógł operować nie tylko podłączonymi bezpośrednio ekspanderami, ale jakąś zdalną komunikacją. Dalej: jak ktoś zauważył, dobrze by było aby pozycje bitów (a tym samym kolejność pinów PCF-a) odpowiadały pozycjom kolorów na semaforze. Zadeklaruję sobie więc odpowiednie stałe: // numery LED dla semafora i tarczy #define SEM_GRN 1 // zielona #define SEM_ORU 2 // górna pomarańczowa #define SEM_RED 3 // czerwona #define SEM_ORD 4 // dolna pomarańczowa #define SEM_WHI 5 // biała #define TAR_RED 1 // czerwona lub niebieska #define TAR_WHI 2 // biała Przy okazji mogę nieco uprościć makra pomocnicze do inicjalizacji tabel semafora. Ponieważ jednocześnie mogą być zapalone maksymalnie dwie ledy, makro będzie miało tylko trzy parametry (pierwsza, druga, migająca): // makro do inicjalizacji tabel // pięć młodszych bitów to zapalane ledy, trzy starsze to numer // migającej ledy (1 do 5) #define SEMLED(a,b,blink) ((1<<((a)-1)) | ((b)?(1<<((b)-1)):0) | ((blink) << 5)) Tym samym innicjalizacja tabel wygląda nieco czytelniej. Przykładowe linijki wyglądają tak: {SEMLED(SEM_GRN,SEM_ORD,SEM_GRN),"S11"} // dla semafora {SEMLED(TAR_RED,TAR_WHI,TAR_WHI),"Sz"} // dla tarczy Niestety - przy okazji wyszedł jeden błąd w projekcie: stała SEM_NEUTRAL określała maskę LED dla pozycji "STOP" semafora. Początkowo czerwone diody zarówno w semaforze, jak i tarczy miały pierwszą pozycję. Teraz jest już inaczej. Mogłem to rozwiązać na kilka sposobów, ale najbardziej elastyczne wydaje mi się wprowadzenie dodatkowej zmiennej w klasie Semafor, określającej numer aktualnie wyświetlanego ustawienia. Ponieważ z założenia sygnał "STOP" jest na pierwszej pozycji, SEM_NEUTRAL ustawiony jest właśnie na numer 1. Wróćmy do numerków pinów i podłączania diod. Ponieważ maksymalny prąd PCF8575 to 100 mA, nie można tak po prostu podłączać sobie diod świecących z maksymalną mocą. Zakładając, że diody łączone są do +5V (jest to możliwe również wtedy, gdy PCF zasilany jest z 3.3V w przypadku użycia ESP zamiast Arduino), należy tak dobrać rezystory, aby prąd diody nie był większy niż 10 mA. Również musimy jakoś sensownie podzielić obciążenie w przypadku większej ilości semaforów i ekspanderów. Aby jakoś to znormalizować, biorąc pod uwagę istnienie par semafor/tarcza postanowiłem, że: semafor podłączony jest do pierwszych pięciu pinów (czyli P0..P4 lub P10..P14) tarcza podłączona jest do dwóch kolejnych pinów (czyli P5/P6 lub P15/P16) W ten sposób ponieważ maksymalnie świeci 8 diod podłączonych do jednego ekspandera, obciążenie wyniesie 80 mA. Aby uprościć zapis stworzyłem makro WHERETO, określające jak podłączony jest dany semafor: #define WHERETO(port,bit) (((port)<<3) | (bit)) Przykładowo: semafor drugi będzie zapisany jako Semafor(semaTable,WHERETO(1,0)), // semafor drugi piny P10..P14 Po tych wszystkich zmianach plik semafor.cpp wygląda następująco: #include <Arduino.h> #include "Makieta.h" /* * Metody klasy Semafor * (można z nich zrobić bibliotekę) */ // pozycja neutralna to pozycja po wciśnięciu klawisza 1 // czyli S1/Ms1 #define SEM_NEUTRAL 1 const char *Semafor::sema2c=" 1234567890ABCD "; // inicjalizacja obiektu, jako "typ" podstawiamy // jedną z tabel programów Semafor::Semafor(const SemaProg *program, uint8_t whereto) { _program=program; _whereto=whereto; _current = SEM_NEUTRAL; ledMask = ledMask = _program[_current].prog & 0x1f; blinkMask=0; } // ustawienie semafora void Semafor::set(uint8_t command) { uint8_t cmd=_program[command].prog; if (!cmd) return; _current=command; ledMask=cmd & 0x1f; int blink = (cmd >>5) & 7; blinkMask = blink ? 1<<(blink-1) : 0; if (_current != SEM_NEUTRAL) startTime = millis(); } void Semafor::update() { if (_current != SEM_NEUTRAL && millis() - startTime > 60000UL) { _current=SEM_NEUTRAL; ledMask = _program[_current].prog & 0x1f; blinkMask=0; } currentLed = ledMask; if (blinkMask && (millis() & 512)) currentLed &= ~blinkMask; } // makro do inicjalizacji tabel // pięć młodszych bitów to zapalane ledy, trzy starsze to numer // migającej ledy (1 do 5) #define SEMLED(a,b,blink) ((1<<((a)-1)) | ((b)?(1<<((b)-1)):0) | ((blink) << 5)) /* * A tutaj dane dla konkretnej makiety */ // numery LED dla semafora i tarczy #define SEM_GRN 1 // zielona #define SEM_ORU 2 // górna pomarańczowa #define SEM_RED 3 // czerwona #define SEM_ORD 4 // dolna pomarańczowa #define SEM_WHI 5 // biała #define TAR_RED 1 // czerwona lub niebieska #define TAR_WHI 2 // biała // tabele programów dla semaforów i tarcz static const SemaProg semaTable[16]={ {0}, {SEMLED(SEM_RED,0,0),"S1"}, {SEMLED(SEM_GRN,0,0),"S2"}, {SEMLED(SEM_GRN,0,SEM_GRN),"S3"}, {SEMLED(SEM_ORU,0,SEM_ORU),"S4"}, {SEMLED(SEM_ORU,0,0),"S5"}, {SEMLED(SEM_GRN,SEM_ORD,0),"S10"}, {SEMLED(SEM_GRN,SEM_ORD,SEM_GRN),"S11"}, {SEMLED(SEM_ORU,SEM_ORD,SEM_ORU),"S12"}, {SEMLED(SEM_ORU,SEM_ORD,0),"S13"}, {SEMLED(SEM_RED,SEM_WHI,SEM_WHI),"Sz"} }; const SemaProg tarczaTable[16]={ {0}, {SEMLED(TAR_RED,0,0),"Ms1"}, {SEMLED(TAR_WHI,0,0),"Ms2"}, {0},{0},{0},{0},{0},{0},{0}, {SEMLED(TAR_RED,TAR_WHI,TAR_WHI),"Sz"} }; // aby uniknąć nieporozumień, makro ustalające które bity // portu PCF-a odpowiadają któremu semaforowi. // Można tego uniknąć wpisując ósemkowo parametr whereto, // ale podejrzewam że byłoby z tego więcejszkody niż pożytku #define WHERETO(port,bit) (((port)<<3) | (bit)) // teraz tablica obiektów klasy Semafor: dwa semafory i dwie tarcze static Semafor semafor[]={ Semafor(semaTable,WHERETO(0,0)), // semafor pierwszy piny P0..P4 Semafor(semaTable,WHERETO(1,0)), // semafor drugi piny P10..P14 Semafor(tarczaTable,WHERETO(0,5)), // tarcza pierwsza piny P5..P6 Semafor(tarczaTable,WHERETO(1,5)) // tarcza druga piny P15..P16 }; const int CNT_SEMA = (sizeof(semafor) / sizeof(semafor[0])); /* * Mapa semaforów * Semafory będą przypisane do pozycji 11 do 14 czyli A do F */ int8_t semaforMap[16]={ 0,0,0,0,0,0,0,0,0,0,0,1,2,3,4,0 }; Dodatkowo musiałem w pliku *.ino zmienić nieco wyświetlanie kontrolne na monitorze serial, ale to już drobiazg: void sendToPcf(uint16_t port) { wwrite16(PCFADR, ~port); char buf[32],*c; int i,n; c=buf; for (i=0;i<5;i++) *c++= (port & (1<<i))?'*':'.'; *c++=' '; for (i=8;i<13;i++) *c++= (port & (1<<i))?'*':'.'; *c++=' '; for (i=5;i<7;i++) *c++= (port & (1<<i))?'*':'.'; *c++=' '; for (i=13;i<15;i++) *c++= (port & (1<<i))?'*':'.'; *c++=0; Serial.println(buf); } Wróćmy jednak do pierwotnego celu dzisiejszej przeróbki, czyli podłączania wyświetlacza. Użyjemy tu popularnego wyświetlacza OLED opartego na układzie SSD1306. Odpowiednie biblioteki które musiny zainstalować to Adafruit_GFX i Adafruit_SSD1306. Niestety - driver SSD1306 w wykonaniu Adafruit jest raczej mało oszczędny jeśli chodzi o pamięć. Przy użyciu wyświetlacza 0.91" (128x32) pamięci w Arduino jeszcze jako-tako wystarcza, ale większe (128x64) już nie będą działać prawidłowo. Na szczęście można "oszukać" wyświetlacz 128x64 ustawiając mu wysokość ekranu na 32 - obraz będzie nieco zniekształcony (wyświetlana co druga linia) ale czytelny. Zresztą - do naszych celów i tak wystarczy ekran 128x32. Wyświetlacz może pracować zarówno z napięciem 3.3V jak i 5V, czyli łączymy: GND - GND Vcc - 5V SCL - A5 SDA - A4 Teraz poprzewz i2c_scanner warto sprawdzić jaki jest rzeczywisty adres wyświetlacza. Po jego ustaleniu tworzymy plik display.cpp z następującą zawartością: #include <Arduino.h> #include "Makieta.h" #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 32 #define OLED_RESET -1 #define SCREEN_ADDRESS 0x3C // do sprawdzenia przez i2cscanner Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); static uint8_t displayStatus = 0; void initDisplay() { if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) { Serial.println("Brak pamięci dla wyświetlacza"); displayStatus=2; return; } displayStatus = 1; display.clearDisplay(); display.setTextSize(2); display.setTextColor(SSD1306_WHITE); display.setCursor(0, 8); display.print("MAKIETA V4"); display.display(); delay(2000); display.clearDisplay(); display.display(); } void displaySemaState(uint8_t sema, uint8_t prog) { if (displayStatus != 1) return; display.clearDisplay(); if (sema) { display.setTextSize(2); display.setTextColor(SSD1306_WHITE); display.setCursor(10, 8); display.write(Semafor::sema2c[sema]); if (prog) { display.write(' '); display.print(semafor[semaforMap[sema]-1].pgname(prog)); } } display.display(); } przy czym w pliku Makieta.h znajdą się dwie dodatkowe linijki: extern void displaySemaState(uint8_t sema=0, uint8_t prog=0); extern void initDisplay(); Jak widać mamy tu na razie tylko dwie funkcję: inicjalizację i wyświetlanie tego, co wciskamy na klawiaturze. Aby to połączyć musimy w pliku *ino włączyć w funkcji setup inicjalizację wyświetlacza, czyli będzie ona wyglądała tak: void setup() { Serial.begin(115200); Wire.begin(); wwrite16(PCFADR, 0xffff); initDisplay(); } Również w pliku getcommand,cpp musimy dopisać wyświetlanie: static void displayKbdStatus() { if (!kbdStatus) { displaySemaState(); Serial.println("--"); } else if (kbdStatus == KBD_SEMA) { Serial.print("SEMA "); Serial.println(Semafor::sema2c[kbdSema]); displaySemaState(kbdSema); } else { displaySemaState(kbdSema, kbdProg); Serial.print("SEMA "); Serial.print(Semafor::sema2c[kbdSema]); Serial.print(" PROG "); Serial.println(semafor[semaforMap[kbdSema]-1].pgname(kbdProg)); } } I to wszystko na dziś. Podaję jeszcze kompletne zawartości pozostałych plików: Plik nagłówkowy #ifndef MAKIETA_H #define MAKIETA_H typedef struct SemaProg { uint16_t prog; const char *name; } SemaProg; // główna klasa Semafor class Semafor { public: Semafor(const SemaProg *program, uint8_t whereto); void set(uint8_t command); void update(); // pobranie aktualnie świecących LED uint8_t get() {return currentLed;}; // przesunięcie dla skonstruowania danych do PCF-a uint8_t whereto() {return _whereto;}; // czy na dajen pozycji jest program? bool prog(uint8_t n) {return _program[n].prog != 0;}; // pobierz nazwę danej pozycji const char *pgname(uint8_t n) {return _program[n].name;}; // pobierz numer aktualnie wyświetlanej pozycji uint8_t current() {return _current;}; // prowizoryczna tablica konwersji liczby 1..14 na znak 1..9 0 A-D static const char *sema2c; private: const struct SemaProg *_program; uint8_t _whereto; uint8_t ledMask; uint8_t blinkMask; uint8_t currentLed; uint8_t _current; uint32_t startTime; }; // makra do wyciągnięcia numeru semafora i polecenia #define SEM_NUM(c) (((c)>>8)-1) #define SEM_CMD(c) ((c) & 0xff) extern const int CNT_SEMA; extern Semafor semafor[]; extern int8_t semaforMap[16]; extern uint16_t getCommand(); extern void displaySemaState(uint8_t sema=0, uint8_t prog=0); extern void initDisplay(); #endif Główny plik *ino #include <Keypad.h> #include "Makieta.h" #include <Wire.h> /* * Dla pojedynczego PCF8575 */ // do sprawdzenia przez i2cscanner #define PCFADR 0x20 static void wwrite16(uint8_t adr, uint16_t data) { Wire.beginTransmission(adr); Wire.write(data & 0xff); Wire.write((data >> 8) & 0xff); Wire.endTransmission(); } void sendToPcf(uint16_t port) { wwrite16(PCFADR, ~port); char buf[32],*c; int i,n; c=buf; for (i=0;i<5;i++) *c++= (port & (1<<i))?'*':'.'; *c++=' '; for (i=8;i<13;i++) *c++= (port & (1<<i))?'*':'.'; *c++=' '; for (i=5;i<7;i++) *c++= (port & (1<<i))?'*':'.'; *c++=' '; for (i=13;i<15;i++) *c++= (port & (1<<i))?'*':'.'; *c++=0; Serial.println(buf); } /* * Realizacja wyświetlania - bardzo uproszczona */ void updateLed() { static uint16_t oldport=0; uint16_t port=0; for (int i=0;i<CNT_SEMA;i++) { port |= semafor[i].get() << semafor[i].whereto(); } if (port != oldport) { oldport=port; sendToPcf(port); } } // no i program główny void setup() { Serial.begin(115200); Wire.begin(); wwrite16(PCFADR, 0xffff); initDisplay(); } void loop() { uint16_t cmd=getCommand(); if (cmd) { // mamy coś do zrobienia semafor[SEM_NUM(cmd)].set(SEM_CMD(cmd)); } // niezależnie od tego czy coś robiliśmy czy nie for (int i=0; i<CNT_SEMA; i++) semafor[i].update(); // no i realizujemy to co nam w semaforach wyszło updateLed(); } Plik klawiatury: #include <Arduino.h> #include <Keypad.h> #include "Makieta.h" static const byte ROWS = 4; static const byte COLS = 4; /* * tu wprowadzam wartości liczbowe odpowiadające klawiszom * bo znaki mi nie są do niczego potrzebne */ #define KEY_HASH '#' #define KEY_ASTERISK '*' static const char keys[ROWS][COLS] = { {1,2,3,11}, {4,5,6,12}, {7,8,9,13}, {KEY_ASTERISK,10,KEY_HASH,14} }; // to dla sklerotyków co lubią odwrotnie wpinać kabelki #if 1 static byte rowPins[ROWS] = {9,8,7,6}; static byte colPins[COLS] = {5,4,3,2}; #else static byte rowPins[ROWS] = {5, 4, 3, 2}; static byte colPins[COLS] = {9, 8, 7, 6}; #endif static Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS ); uint8_t kbdStatus=0; uint8_t kbdSema, kbdProg; uint32_t lastKeyPressed=0; enum { KBD_RELAX=0, KBD_SEMA, KBD_PROG }; static void displayKbdStatus() { if (!kbdStatus) { displaySemaState(); Serial.println("--"); } else if (kbdStatus == KBD_SEMA) { Serial.print("SEMA "); Serial.println(Semafor::sema2c[kbdSema]); displaySemaState(kbdSema); } else { displaySemaState(kbdSema, kbdProg); Serial.print("SEMA "); Serial.print(Semafor::sema2c[kbdSema]); Serial.print(" PROG "); Serial.println(semafor[semaforMap[kbdSema]-1].pgname(kbdProg)); } } uint16_t getCommand() { int g=keypad.getKey(); if (!g) { if (kbdStatus && millis() - lastKeyPressed > 5000UL) { kbdStatus=KBD_RELAX; displayKbdStatus(); } return 0; } lastKeyPressed = millis(); if (!kbdStatus) { if (g == KEY_HASH || g == KEY_ASTERISK) return 0; if (!semaforMap[g]) return 0; kbdStatus = KBD_SEMA; kbdSema = g; displayKbdStatus(); return 0; } if (g == KEY_HASH) { kbdStatus = KBD_RELAX; displayKbdStatus(); return 0; } if (kbdStatus == KBD_SEMA) { if (g == KEY_ASTERISK) return 0; if (!semafor[semaforMap[kbdSema]-1].prog(g)) return 0; kbdStatus = KBD_PROG; kbdProg = g; displayKbdStatus(); return 0; } if (g == KEY_ASTERISK) { kbdStatus = KBD_RELAX; displayKbdStatus(); return (semaforMap[kbdSema] << 8) | kbdProg; } if (!semafor[semaforMap[kbdSema]-1].prog(g)) return 0; kbdProg = g; displayKbdStatus(); return 0; } No i jak zwykle zip przygotowany do wrzucenia do Arduino:prezesedi4.zip Ponieważ mamy już wyświetlacz, warto go użyć do wyświetlania jakichś przydatnych informacji a nie tylko wciskanych klawiszy. Ale to może już nie dziś - czyli stay tuned! 4 Cytuj Link do komentarza Share on other sites More sharing options...
prezesedi Czerwiec 20, 2023 Autor tematu Udostępnij Czerwiec 20, 2023 (edytowany) Podpiąłem wszystko jak należy, wgrałem brakujące biblioteki. Po załadowaniu kodu na wyświetlaczu pojawił się napis "MAKIETA V4" Ledy podpięte do P15 i P16, wprowadzenie 'D' '0' '*' daje czerwoną migającą, a na wyświetlaczu "D Sz" MISTRZOSTWO EDIT. Pytanie: czy możemy zamienić (na wyświetlaczu "D Sz") na (na wyświetlaczu "Tar.2 Sz") Edytowano Czerwiec 20, 2023 przez prezesedi Cytuj Link do komentarza Share on other sites More sharing options...
ethanak Czerwiec 20, 2023 Udostępnij Czerwiec 20, 2023 3 godziny temu, prezesedi napisał: czy możemy zamienić (na wyświetlaczu "D Sz") na (na wyświetlaczu "Tar.2 Sz") I właśnie o tym jest (między innymi) następna część 🙂 Spróbujmy teraz zmusić wyświetlacz do wyświetlania bardziej sensownych rzeczy. Zacznijmy od tego, że numerek semafora niewiele nam mówi, warto by było wprowadzić jakąś nazwę. W tym celu w klasie Semafor musimy: dodać dodatkową zmienną prywatną, w której będzie zapamiętana nazwa; dodać parametr do konstruktora; i wreszcie dopisać funkcję, która zwróci nam nazwę semafora. Deklaracja klasy semafor wyglądać teraz będzie tak: class Semafor { public: Semafor(const SemaProg *program, uint8_t whereto, const char *name); void set(uint8_t command); void update(); // pobranie aktualnie świecących LED uint8_t get() {return currentLed;}; // przesunięcie dla skonstruowania danych do PCF-a uint8_t whereto() {return _whereto;}; // czy na dajen pozycji jest program? bool prog(uint8_t n) {return _program[n].prog != 0;}; // pobierz nazwę danej pozycji const char *pgname(uint8_t n) {return _program[n].name;}; // pobierz numer aktualnie wyświetlanej pozycji uint8_t current() {return _current;}; // pobierz nazwę semafora const char *name() {return _name;}; // prowizoryczna tablica konwersji liczby 1..14 na znak 1..9 0 A-D // static const char *sema2c; nie będzie już potrzebna private: const struct SemaProg *_program; uint8_t _whereto; uint8_t ledMask; uint8_t blinkMask; uint8_t currentLed; uint8_t _current; uint32_t startTime; const char *_name; }; a funkcja konstruktora: Semafor::Semafor(const SemaProg *program, uint8_t whereto, const char *name) { _program=program; _whereto=whereto; _name=name; _current = SEM_NEUTRAL; ledMask = ledMask = _program[_current].prog & 0x1f; blinkMask=0; } Teraz zamiast wyświetlać jakiś głupi numerek możemy w funkcji wyświetlania na monitorze serial zamienić wszystkie wystąpienia Semafor::sema2c[kbdSema] na semafor[semaforMap[kbdSema]-1].name() Podobnie w funkcji wyświetlacza, z tym że tam dodatkowo musimy zmienić wypisywanie jednego znaku display.write(Semafor::sema2c[kbdSema]) na wypisywanie całej nazwy: display.print(semafor[semaforMap[kbdSema]-1].name()); Pozostaje tylko usunięcie z pliku semafor.cpp niepotrzebnej już linii: const char *Semafor::sema2c=" 1234567890ABCD "; oraz oczywiście dodanie nazw w deklaracji tablicy, np: static Semafor semafor[]={ Semafor(semaTable,WHERETO(0,0),"Sem1"), // semafor pierwszy piny P0..P4 Semafor(semaTable,WHERETO(1,0),"Sem2"), // semafor drugi piny P10..P14 Semafor(tarczaTable,WHERETO(0,5),"TM1"), // tarcza pierwsza piny P5..P6 Semafor(tarczaTable,WHERETO(1,5),"TM2") // tarcza druga piny P15..P16 }; I już możemy cieszyć się ładnymi nazwami na wyświetlaczu i w monitorze serial. Przy okazji: nazwa semafora nie może być dłuższa niż 5 znaków (więcej się nie zmieści na wyświetlaczu), ale to powinno wystarczyć. No, ale to dopiero początek. Przede wszystkim po wprowadzeniu i zatwierdzeniu ustawienia semafora wyświetlacz po prostu gaśnie, a chciałoby się dostać jakąś informację że coś zostało zapisane. Oprócz tego warto móc wyświetlić sobie bieżący stan wszystkich semaforów. Zacznijmy od wprowadzenia dodatkowych zmiennych. Niech displayMode oznacza, co się w danej chwili wyświetla. Oprócz tego - jako że wyświetlacze OLED mają tendencję do wypalania - trzeba automatycznie po jakimś czasie wygasić wyświetlacz, a więc musimy zapamiętać kiedy zostało wprowadzone polecenie wyświetlania. W pliku display.cpp dodajemy gdzieś na początku: enum { DPM_BLANK=0, // wyświetlacz wygaszony DPM_KBD, // stan klawiatury DPM_SET, // ustawienie zostało zaakceptowane DPM_SHOW // wyświetlanie wszystkich semaforów }; static uint8_t displayMode = DPM_BLANK; static uint32_t displayTimer=0; Potrzebna teraz będzie funkcja, która będzie pilnować wygaszania wyświetlacza. W tym samym pliku dodajemy: void updateDisplay() { uint32_t delta; if (displayStatus != 1) return; switch(displayMode) { case DPM_SET: delta = 2000UL; // dwie sekundy "Zapisano" break; case DPM_SHOW: delta = 10000UL; // dziesięć sekund stanu break; default: return; } if (millis() - displayTimer >= delta) { displayMode = DPM_BLANK; display.clearDisplay(); display.display(); return; } } Oczywiście funkcję należy zadeklarować w pliku Makieta.h: extern void updateDisplay(); i wywołanie funkcji dopisać na końcu funkcji loop() w *.ino. Czyli mamy już bazę do dalszej pracy. Musimy teraz nieco zmienić funkcję realizującą samo wyświetlanie. Dodając dodatkowy parametr informujemy funkcję, że ma wyświetlić potwierdzenie. Czyli: void displaySemaState(uint8_t sema, uint8_t prog, bool done) { if (displayStatus != 1) return; display.clearDisplay(); if (sema) { displayMode = DPM_KBD; display.setTextSize(2); display.setTextColor(SSD1306_WHITE); display.setCursor(10, done ? 16 : 8); display.print(semafor[semaforMap[sema]-1].name()); if (prog) { display.write(' '); display.print(semafor[semaforMap[sema]-1].pgname(prog)); if (done) { display.setCursor(16,0); display.print("Zapisano"); displayMode = DPM_SET; displayTimer = millis(); } } } else { displayMode = DPM_BLANK; } display.display(); } Pamiętajmy o zmianie deklaracji funkcji w Makieta.h: extern void displaySemaState(uint8_t sema=0, uint8_t prog=0, bool done=false); Jak widać, funkcja z parametrem "done" równym true zapisuje stan wyświetlacza w displayMode, zapamiętuje moment wydania polecenia i wyświetla odpowiednią informację. Funkcja updateDisplay() będzie pilnować, aby wygasić wyświetlacz po dwóch sekundach. Musimy jeszcze nieco zmodyfikować funkcje obsługi klawiatury - muszą teraz podawać więcej informacji do funkcji wyświetlania. W pliku getcommand.cpp musimy wprowadzić nieco zmian. Przede wszystkim funkcja displayKbdStatus musi wyłapać zatwierdzenie ustawienia: static void displayKbdStatus(bool done=false) { if (done) { displaySemaState(kbdSema, kbdProg, true); Serial.print("ZAPISANO "); Serial.print(semafor[semaforMap[kbdSema]-1].name()); Serial.print(" "); Serial.println(semafor[semaforMap[kbdSema]-1].pgname(kbdProg)); } else if (!kbdStatus) { displaySemaState(); Serial.println("--"); } else if (kbdStatus == KBD_SEMA) { Serial.print("SEMA "); Serial.println(semafor[semaforMap[kbdSema]-1].name()); displaySemaState(kbdSema); } else { displaySemaState(kbdSema, kbdProg); Serial.print("SEMA "); Serial.print(semafor[semaforMap[kbdSema]-1].name()); Serial.print(" PROG "); Serial.println(semafor[semaforMap[kbdSema]-1].pgname(kbdProg)); } } Natomiast funkcja getCommand() musi poprzednią funkcję o tym poinformować. W tym celu zmieniamy: if (g == KEY_ASTERISK) { kbdStatus = KBD_RELAX; displayKbdStatus(); return (semaforMap[kbdSema] << 8) | kbdProg; } na: if (g == KEY_ASTERISK) { displayKbdStatus(true); kbdStatus = KBD_RELAX; return (semaforMap[kbdSema] << 8) | kbdProg; } Po dokonaniu tych zmian powinno się nam ładnie wyświetlać potwierdzenie ustawienia. Ale to jeszcze nie wszystko. Chcemy, aby istniała możliwość wyświetlenia stanu wszystkich semaforów np. po naciśnięciu gwiazdki. Niestety - funkcja getCommand zwracała nam dotychczas tylko polecenie zmiany ustawienia konkretnego semafora. Na szczęście nie wszystkie bity są tu znaczące, umówmy się więc, że ustawiony najstarszy bit będzie oznaczał wykonanie innej funkcji. Przede wszystkim musimy zmodyfikować funkcję getCommand. W pliku Makieta.h wprowadzamy dodatkowe definicje: #define CMD_IS_FUNCTION(a) ((a) & 0x8000) #define CMD_DISPLAYALL 0x8000 a w getCommand musimy zamienić: if (!kbdStatus) { if (g == KEY_HASH || g == KEY_ASTERISK) return 0; na: if (!kbdStatus) { if (g == KEY_HASH) return 0; if (g == KEY_ASTERISK) return CMD_DISPLAYALL; Natomiast w głównej pętli loop() zamieniamy: if (cmd) { // mamy coś do zrobienia semafor[SEM_NUM(cmd)].set(SEM_CMD(cmd)); } na coś bardziej skomplikowanego: if (cmd) { // mamy coś do zrobienia if (CMD_IS_FUNCTION(cmd)) { displayAllSema(); } else { semafor[SEM_NUM(cmd)].set(SEM_CMD(cmd)); } } Pozostaje nam tylko dopisanie funkcji displayAllSema w pliku display.cpp: void displayAllSema() { int i; display.clearDisplay(); display.setTextSize(1); display.setTextColor(SSD1306_WHITE); for (i=0;i<CNT_SEMA;i++) { display.setCursor(64 * (i&1), (i/2) * 8); display.print(semafor[i].name()); display.print(' '); display.print(semafor[i].pgname(semafor[i].current())); } display.display(); displayMode = DPM_SHOW; displayTimer = millis(); } oraz zadeklarowanie jej w Makieta.h: extern void displayAllSema(); Po skompilowaniu i załadowaniu powinniśmy uzyskać zadowalający efekt. Tym razem nie będę pokazywać pełnych treści poszczególnych plików, załączę jedynie plik przygotowany do wgrania na Arduino: prezesedi5.zip To tyle na dzisiaj - dość duża przeróbka, więc na razie wystarczy. Oczywiście - program jest przystosowany do maksymalnie czterech semaforów i jednego ekspandera. W dodatku funkcja realizująca wyświetlanie stanu wszystkich semaforów nie reaguje na zmianę stanu w trakcie wyświetlania. Trzeba będzie temu zaradzić. I jeszcze jedno: następna wersja będzie już ta ostateczną, do której będziemy mogli wykorzystać Arduino Uno. Następne będą wymagać ESP32, ale za to będą miały więcej możliwości. Tak że jak zwykle - stay tuned! 1 Cytuj Link do komentarza Share on other sites More sharing options...
prezesedi Czerwiec 20, 2023 Autor tematu Udostępnij Czerwiec 20, 2023 Po 22giej udzielę informacji nt. działania wersji "prezesedi5". Na tę chwilę, będąc w pracy, mogę jedynie poczytać na telefonie. Niestety uroki bezpieczeństwa danych 😒 1 Cytuj Link do komentarza Share on other sites More sharing options...
Polecacz 101 Zarejestruj się lub zaloguj, aby ukryć tę reklamę. Zarejestruj się lub zaloguj, aby ukryć tę reklamę. 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
ethanak Czerwiec 20, 2023 Udostępnij Czerwiec 20, 2023 3 godziny temu, prezesedi napisał: Niestety uroki bezpieczeństwa danych Skąd ja to znam... Kiedyś dawno temu, jak GaduGadu działał na jakimś swoim dziwnym porcie, kierownik w pewnej firmie dał zadanie adminowi, żeby wyciął GaduGadu bo pracownicy mają nie gadać a pracować. Zgłosił się do mnie na GG znajomy (był nocnym stróżem w tej firmie), że nic poza GG mu nie działa. Bliższe zapoznanie się z tematem pozwoliło stwierdzić, że genialny admin zamiast na routerze zablokować porty GG zablokował całą resztę oprócz tych portów 🙂 Na szybko postawiliśmy jakiegoś VPN-a na tym porcie, ale nigdy się nie dowiedziałem co ów admin usłyszał jak przyszedł do roboty 🙂 3 godziny temu, prezesedi napisał: Po 22giej udzielę informacji A ja się właśnie zastanawiam nad apką na smartfona do sterowania semaforami - walor raczej edukacyjny ale pozwoli pokazać co potrafi ESP32 🙂 Cytuj Link do komentarza Share on other sites More sharing options...
prezesedi Czerwiec 20, 2023 Autor tematu Udostępnij Czerwiec 20, 2023 4 minuty temu, ethanak napisał: A ja się właśnie zastanawiam nad apką na smartfona do sterowania semaforami - walor raczej edukacyjny ale pozwoli pokazać co potrafi ESP32 🙂 Hmmmm. W sumie modele ze względu na sposób zasilania dzieli się na dwa rodzaje: zasilane AC zasilane DC (mnie te interesują). i właśnie przy zasilaniu DC (DCC) prym w sterowaniu wiodą dwie firmy (Piko i Roco). Osobiście posiadam sterowanie Piko, ale wiem, że ci posiadający sterowanie Roco wraz z odpowiednim routerem (model Z21) mogą za pomocą smartfona/tabletu sterować wszystkim. Niestety nie miałem nigdy z tym styczności, więc to tylko taka ciekawostka. Przepraszam, za offtopic. To tak w nawiązaniu do aplikacji na smartfon. Cytuj Link do komentarza Share on other sites More sharing options...
ethanak Czerwiec 20, 2023 Udostępnij Czerwiec 20, 2023 4 minuty temu, prezesedi napisał: To tak w nawiązaniu do aplikacji na smartfon. To ja powiem tak: w sumie jeśli możesz czymś sterować za pomocą ESP32 (a najprawdopodobniej możesz) to prawdopodobnie możesz tym sterować za pomocą smartfona, tabletu czy czegokolwiek. Kwestia potrzeby i chęci... 1 Cytuj Link do komentarza Share on other sites More sharing options...
_LM_ Czerwiec 20, 2023 Udostępnij Czerwiec 20, 2023 33 minuty temu, ethanak napisał: A ja się właśnie zastanawiam nad apką na smartfona Przepraszam że się wtrącę, można by powiedzieć że to aż prosi się na (z)użycie jakiegoś starego tableta z w miarę dużym ekranem. Wtedy to byłby pulpit na miarę 21 wieku. Cytuj Link do komentarza Share on other sites More sharing options...
ethanak Czerwiec 20, 2023 Udostępnij Czerwiec 20, 2023 @_LM_ zgłaszasz sie na ochotnika? 😉 Cytuj Link do komentarza Share on other sites More sharing options...
_LM_ Czerwiec 20, 2023 Udostępnij Czerwiec 20, 2023 (edytowany) Wiedziałem że o to zapytasz, i wiem też że może to u mnie trwać zdecydowanie ponad czas waszej cierpliwości 😄 no i nie jestem zbyt dobry w UX no ale siłą rzeczy zapytam: to miałby być web app czy połączenie przez BT || WiFi UDP? Edytowano Czerwiec 20, 2023 przez _LM_ Cytuj Link do komentarza Share on other sites More sharing options...
ethanak Czerwiec 20, 2023 Udostępnij Czerwiec 20, 2023 Co prawda większość tego typu rozwiązań to BT, ale ja bym postawił AP na ESP32 i leciał po tcp/udp. Cytuj Link do komentarza Share on other sites More sharing options...
H1M4W4R1 Czerwiec 20, 2023 Udostępnij Czerwiec 20, 2023 3 minuty temu, ethanak napisał: Co prawda większość tego typu rozwiązań to BT, ale ja bym postawił AP na ESP32 i leciał po tcp/udp. IMHO osobiście bym napisał REST API na ESP (AP) i sterował tym po HTTP 😄 Ale każdy ma inne preferencje. Cytuj Link do komentarza Share on other sites More sharing options...
_LM_ Czerwiec 20, 2023 Udostępnij Czerwiec 20, 2023 @H1M4W4R1 czy to by oznaczało że cała strona musiałaby siedzieć w ESP? Ma to swoje zalety że jesteśmy niezależni od posiadanego sprzętu, tablet, pc i inne. No ale REST API to nie moja bajka. Cytuj Link do komentarza Share on other sites More sharing options...
ethanak Czerwiec 20, 2023 Udostępnij Czerwiec 20, 2023 Właśnie wolałbym uniknąć tcp przy sterowaniu a pozostawił tylko do przesłania plików. Udp sprawdzi się lepiej przy krótkich pakietach typu "klawisz wciśnięty". Cytuj Link do komentarza Share on other sites More sharing options...
_LM_ Czerwiec 20, 2023 Udostępnij Czerwiec 20, 2023 (edytowany) Całość komunikacji zrobić przez UDP i po sprawie, tyle że dochodzi parser po stronie ESP ale to chyba nie problem? Edytowano Czerwiec 20, 2023 przez _LM_ Cytuj Link do komentarza Share on other sites More sharing options...
Pomocna odpowiedź
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!