Skocz do zawartości

Arduino w modelarstwie kolejowym


prezesedi

Pomocna odpowiedź

Teraz będzie krócej.

Ponieważ sterowanie rozkładem jazdy i zapowiedziami pociągów jest podobne, postanowiłem zrobić wspólny kod dla tych funkcji. Przy okazji wyszło mi, że pomyliłem się obliczając wolne miejsce na ekranie i musiałem podnieść rozkład jazdy o jeden piksel w górę, ale to szczegół.

Wrócę więc do kodu. Ponieważ używam tej samej funkcji do rysowania rozkładu, postanowiłem dodać do funkcji parametr mówiący którą planszę rysuje. Tak więc w touchLoop pojawił się fragment:

        switch(pos) {
            case 2:
            drawTSched(TSM_SCHEDULE);
            break;
            
            case 3:
            drawTMsgList();
            break;

            case 4:
            drawTSched(TSM_ANNOUNCES);
            break;
            
            default:
            drawTError("NIE ZAIMPLEMENTOWANO");
            
        }

Natomiast sama funkcja drawTSched zyskała również dodatkowe parę linijek:

static const char * const annoMenu[]={"Odjazd","Przyjazd", "Post\363j"};

static void drawTSched(int tsm)
{
    if (!rozklad.valid || !rozklad.validPos) {
        drawTError("BRAK ROZK\221ADU");
        return;
    }
    touchScreenMode=tsm; //ustawiam zmienną globalną
    tft.fillRect(0,18,320,240-18,0);
    tschedStart=tschedMarked=rozklad.pos;
    drawUDKey(19,0);
    drawUDKey(53,1);
    drawUDKey(53+3*34,2);
    drawUDKey(53+4*34,3);
    // i jeśli to zapowiedzi, rysuję na dole dodatkowe klawisze zapowiedzi pociągów:
    if (tsm == TSM_ANNOUNCES) {
        int i;
        for (i=0;i<3;i++) {
            drawHButton(80*i+41,240-18,78,18,annoMenu[i],false);
        }
    }
    drawTSchedPart();    
}

Kolejna mała modyfikacja dotyczy funkcji drawTSchedLine. Uznałem, że warto odróżnić kolorami wybraną pozycję na dwóch planszach. Tak więc odpowiednia linijka ustalająca kolor tła wygląda teraz tak:

    tft.fillRect(0,y,290,33,(spos == tschedMarked)?
        ((touchScreenMode == TSM_SCHEDULE) ? RGB565(255,255,0) : RGB565(128,255,128)):
        GRAY565(200));

I to już wszystko jeśli chodzi o rysowanie. Efekt można zobaczyć na zdjęciu:

tftanno.thumb.jpg.ce6eec3cd605bb673c8d8fe5dfbacf6c.jpg

Kolej na reakcję programu na dotyk. Ponieważ użyję tej samej funkcji co w sterowaniu rozkładem, ostatnia instrukcja switch w touchLoop będzie wyglądać następująco:

    switch (touchScreenMode) {
        case TSM_MESSAGES:
        touchDoMessages();
        break;
        case TSM_SCHEDULE:
        case TSM_ANNOUNCES:
        touchDoSchedule();
        break;
    }

Trochę więcej przeróbek jest potrzebne w samej funkcji touchDoSchedule. Przede wszystkim należy dodać odczyt przycisków zapowiedzi. W tym celu stworzyłem dodatkową funkcję:

static int getTouchAnnoKey()
{
    if (scry <240 -16) return 0;
    int x = scrx-43;
    if (x < 0) return 0;
    int mn = x / 80;
    if (mn > 2) return 0;
    if ((x % 80) > 73) return 0;
    return mn + 20;
}

Jak widać funkcja zwraca wartości 20, 21 lub 22 dla kolejnych przycisków. W samej funkcji touchDoSchedule wywołanie będzie wyglądać następująco:

static void touchDoSchedule()
{
    int mn=getTouchSchedPos();
    if (!mn) mn=getTouchSchedKey();
    if (!mn && touchScreenMode == TSM_ANNOUNCES) {
        mn=getTouchAnnoKey();
    }

Funkcja będzie wywołana tylko wtedy, gdy program znajduje się w stanie ANNOUNCE.

Następna modyfikacja dotyczy skrolowania rozkładu. Tym razem chciałęm, aby zaznaczona pozycja była zawsze na ekranie, a więc część odpowiadająca za skrolowanie będzie taka:

	if (mn >= 10 && mn <20) {
        if (mn == 10) tschedStart=(tschedStart+rozklad.lines-5) % rozklad.lines;
        else if (mn == 11) tschedStart=(tschedStart+rozklad.lines-1) % rozklad.lines;
        else if (mn == 14) tschedStart=(tschedStart+1) % rozklad.lines;
        else if (mn == 15) tschedStart=(tschedStart+5) % rozklad.lines;
        if (touchScreenMode == TSM_ANNOUNCES) {
            correctTSchedMark(mn > 12);
        }
        touchWaitStatus=TWS_TOUCHED;
        drawTSchedPart();
        return;
    }

Funkcja correctTschedMark wywoływana tylko w trybie ANNOUNCE jako parametr przyjmuje kierunek skrolowania, a jej zadaniem jest utrzymanie zaznaczonej pozycji w obrębie ekranu:

static void correctTSchedMark(bool up)
{
    int pos2 = tschedMarked;
    if (pos2 < tschedStart) pos2 += rozklad.lines;
    if (pos2 > tschedStart+5) { // poza ekranem
        tschedMarked = up? tschedStart : ((tschedStart+5) % rozklad.lines);
    }
}

Teraz reakcja na dotknięcie przycisku zapowiedzi:

    if (mn >= 20) {
        if (touchScreenMode == TSM_ANNOUNCES) {
            touchWaitStatus=TWS_TOUCHED;
            sayTrain(tschedMarked, mn-20);
        }
        return;
    }

i na koniec na wybranie pozycji:

    if (touchScreenMode == TSM_SCHEDULE) {
        rozklad.set((tschedStart+mn-1) % rozklad.lines);
        tschedMarked=rozklad.pos;
    }
    else {
        tschedMarked = (tschedStart+mn-1) % rozklad.lines;
    }
    touchWaitStatus=TWS_TOUCHED;
    drawTSchedPart();

I to już wszystko - jak widać konieczne zmiany były niewielkie.

Kod jak zwykle w załączniku: kolejpan10.zip

Oczywiście zdaję sobie sprawę z tego że kod nie jest najładniejszy - ale jak może ktoś pamięta, jednym z moich celów było przedstawienie procesu powstawania aplikacji. Tak że "upiększaniem" zajmę się następnym razem.

Niestety - na razie muszę przełożyć wyświetlacz i wzmacniacz do innego urządzenia, tak więc dalsza część programu za parę dni. W każdym razie; stay tuned!
 

  • Lubię! 2
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

Dobry wieczór. Kod już na pokładzie ESP32. Wgrał się bez problemów i sterowanie semaforów na wyświetlaczu jest idealne.

Jutro z rana postaram się "sklecić" jakiś prosty układ torowy i zobaczyć działanie "po swojemu"

Link do komentarza
Share on other sites

Dobry Wieczór.

Kod ściągnięty i przetestowany. MISTRZOSTWO ŚWIATA.

@ethanak twoje umiejętności i pomysły są niewyobrażalne. To jest niesamowite co stworzyłeś.

Link do komentarza
Share on other sites

No tak - miał być koniec, ale gdzieś mi uciekł...

Przede wszystkim po chwili zabawy stwierdziłem, że przy wygłaszaniu komunikatu dobrze by było, aby właściwa pozycja była podświetlona. Tu akurat sprawa okazała się prosta. Drobna zmiana w funkcji 

static void drawTMsgList(int selected=0)
{
    touchScreenMode = TSM_MESSAGES;
    touchLastPos=0;
    if (!selected) tft.fillRect(0,18,320,240-18,0);

Jak widać funkcja dostała parametr określający, który komunikat ma być podświetlony (domyślnie żaden). Jeśli komunikat ma być podświetlony nie trzeba wyczerniać fragmentu ekranu z listą komunikatów; takowa jest już wyświetlona, a wyczzernienie spowodowałoby brzydkie mignięcie,

Dalej w pętli wyświetlania:

        tft.fillRect(0,20+24*i,320,22,
            (selected == i+1) ? RGB565(0,0,255) : GRAY565(200));
        tft.setTextColor((selected == i+1) ? RGB565(255,255,0):0);

Tło podświetlonego komunikatu będzie niebieskie, napis żółty.

Jednocześnie wszędzie tam, gdzie wywoływana jest funkcja komunikatu, dodałem wywołanie drawTMsgList:

        if (key <= 9) {
            drawTMsgList(key);
            sayText(komunikaty[key-1].c_str(),true);
        }

I tu mógłbym skończyć, ale popatrzyłem dokładniej na kod i stwierdziłem, że to koniecznie trzeba poprawić. Wyszukiwanie dotkniętego prostokąta na zasadzie siatki jest dobre, jeśli mamy ich niewiele i są w stałych miejscach ekranu (np. menu). Z drugiej strony tworzenie jakiejś klasy "touchButton" trochę mijałoby się z celem - trzeba by było tworzyć jakieś dynamiczne listy obiektów, co raczej nie pomogłoby w czytelności programu. Wybrałem więc inną drogę.

Przede wszystkim stwierdziłem, że istnienie dwóch oddzielnych procedur obsługi dotyku i klawiatury to o jedna za dużo. W sumie reakcja na klawiaturę z wyjątkiem jednego przypadku (zapowiedź pociągu) jest identyczna... postanowiłem więc zrezygnować z owego dualizmu.

Wyniknął z tego prosty wniosek: ponieważ każdy element interaktywny na ekranie ma przyporządkowany konkretny klawisz, warto stworzyć jakąś strukturę, która zawiera granice przycisku i kod przyporządkowanego klawisza. Po namyśle stworzyłem coś takiego:

struct touchArea {
    int16_t tminx, tmaxx,tminy,tmaxy;
    const char *text;
    uint8_t key;
};

static struct touchArea touchArea[16]; //więcej klwaiwzy nie ma
static int buttonCount; // ilość aktualnie wyświetlanych pól

Dodawanie pola do tablicy realizuje prosta funkcja:

static void addTouchButton(int x, int y, int w, int h,
    uint8_t key,const char *text, bool selected)
{
    touchArea[buttonCount].tminx=x?x+2:0;
    touchArea[buttonCount].tminy=y?y+2:0;
    touchArea[buttonCount].tmaxx=(x+w >=320)?0:(x+w-2);
    touchArea[buttonCount].tmaxy=(y+h >=240)?0:(y+h-2);
    touchArea[buttonCount++].key=key;
    if (text) {
        drawHButton(x,y,w,h,text,selected);
    }
}

Jak widać, pole dotyku jest dodatkowo zmniejszone (ekrany rezystancyjne nie grzeszą dokładnością). Dodatkowo można dodać albo narysowany klawisz, albo tylko dodać pole (gdy funkcja korzysta z własnych procedur rysowania).

W tej sytuacji stwierdzenie, które pole zostało dotknięte realizuje prosta funkcja:

static int touchedButton()
{
    int i;
    for (i=0;i<buttonCount;i++) {
        if ((!touchArea[i].tminx || scrx >= touchArea[i].tminx) && 
            (!touchArea[i].tmaxx || scrx <  touchArea[i].tmaxx) &&
            (!touchArea[i].tminy || scry >= touchArea[i].tminy) && 
            (!touchArea[i].tmaxy || scry <  touchArea[i].tmaxy)) {
                return touchArea[i].key;
        }
    }
    return 0;
}

Oczywiście ta funkcja nie może być bezpośrednio zastosowana do detekcji pola - pierwsze dotknięcie może błędnie podać pozycję, Dlatego właściwa funkcja zwraca kod klawisza dopiero wtedy, gdy kilka razy pod rząd otrzyma ten sam kod:

static int lastTouchButton=0;

static int getTouchedButton()
{
    if (!getScreenXY(&scrx, &scry)) {
        if (touchScreenMode != TSM_SEMA && millis() - lastInputActivity > 20000UL) {
            drawMainMenu(1);
            drawTSemas();
        }
        touchCtl=0;
        lastTouchButton=0;
        return 0;
    }
    lastInputActivity=millis();
    int m=touchedButton();
    if (!m) {
        touchCtl=0;
        lastTouchButton=0;
        return 0;
    }
    if (m != lastTouchButton) {
        lastTouchButton=m;
        touchCtl=0;
    }
    else {
        if (touchCtl++ >= 4) {
            touchWaitStatus = TWS_TOUCHED;
            touchCtl=0;
            return m;
        }
    }
    return 0;
}


Podobnie można uprościć kod dla klawiatury. Funkcja musi zwrócić kod klawisza o ile nie jest to KEY_ASTERISK (obsługiwany oddzielnie), a przy okazji zatrzymać komunikat:

static int getTouchKey()
{
    int key=getKey();    
    if (!key) return 0;
    lastInputActivity = millis();

    if (key == KEY_ASTERISK) {
        if (touchScreenMode <= TSM_ERROR) drawInfoScreen();
        if (touchScreenMode <= TSM_ERROR) drawInfoScreen();
        return 0;
    }
    
    if (key == KEY_HASH) stopSpeech();
    return key;
}

Teraz mogłem już uprościć do maksimum funkcję touchLoop():

void touchLoop()
{
    int m=getTouchKey();
    if (!m && have_calpos) m=getTouchedButton();
    if (m) realizeKey(m);
    refreshTSema();
}

a całą obsługę zawrzeć w jednej stosunkowo prostej funkcji realizeKey:

void realizeKey(int key)
{
    if (key >= KEY_A && key <=KEY_D) {
        drawMainMenu(key-KEY_A+1);
        switch(key) {
            case KEY_A:
            drawTSemas();
            break;
    
            case KEY_B:
            drawTSched(TSM_SCHEDULE);
            break;
                
            case KEY_C:
            drawTMsgList();
            break;
            
            case KEY_D:
            drawTSched(TSM_ANNOUNCES);
            break;
        }
        return;
    }
    switch(touchScreenMode) {
        case TSM_SEMA:
        if (key <= CNT_SEMA) {
            lastTouchSema = key;
            drawTSemaSet();
        }
        break;

        case TSM_SETSEMA:
        if (key == KEY_HASH) {
            drawTSemas();
            break;
        }
        printf("KEY %d, SK=%d, NUM=%d\n",
            key, touchSemaKeys[key-1], touchSemaKeyNum);
        if (key >= touchSemaKeyNum) break;
        semafor[lastTouchSema-1].set(touchSemaKeys[key-1]);
        drawTSemas();
        break;

        case TSM_MESSAGES:
        if (key <= 9) {
            drawTMsgList(key);
            sayText(komunikaty[key-1].c_str(),true);
        }
        break;

        case TSM_ANNOUNCES:
        case TSM_SCHEDULE:
        keyboardDoSched(key);
        break;

        case TSM_SETANNO:
        if (key == KEY_HASH || key <= 3) {
            if (key != KEY_HASH) sayTrain(tschedMarked, key-1);
            drawTSched(TSM_ANNOUNCES, tschedStart, tschedMarked);
        }
        break;
    }
}

Funkcja jest praktycznie taka sama jak stosowane w poprzedniej wersji, tak więc pozwolę sobie nic na jej temat nie mówić.

Oczywiście oprócz tego wszystkiego trzeba było dopisać wywołania addTouchButton wszędzie gdzie są rysowane jakieś elementy interaktywne. Nie będę może pokazywać wszystkich, a jedynie parę przykładów.

Funkcja rysująca menu:

static void drawMainMenu(int pos=0)
{
    tft.fillRect(0,0,320,18,0);
    buttonCount=0; // reset indeksu tblicy touchArea
    int i;
    for (i=0;i<4;i++) {
        addTouchButton(80*i+1,0,78,18,KEY_A+i,mainMenu[i],pos == i+1);
    }
}

fragment funkcji rysującej klawisze nawigacji po rozkładzie:

static void drawUDKey(int y, int nr)
{
    static const uint8_t keys[]={KEY_PGUP, KEY_UP, KEY_DOWN, KEY_PGDN};
    addTouchButton(292,y,28,33,keys[nr],NULL,false);


Fragment funkcji wyświetlania pozycji komunikatu:

static void drawTMsgList(int selected=0)
{
    buttonCount=4; // reset do klawiszy menu
    ...
    for (i=0; i<9;i++) {
        // "niewidzialny" przycisk wielkości wyświetlanej pozycji
        addTouchButton(0,20+24*i,320,22,i+1, NULL, false);
        tft.fillRect(0,20+24*i,320,22,
            (selected == i+1) ? RGB565(0,0,255) : GRAY565(200));


Po skompilowaniu i załadowaniu programu wszystko pięknie ruszyło. Zadowolony z siebie postanowiłem zrobić sobie kawusię i usiąść do opisu zmian, ale nagle odezwał się kolega wątkotwórca: otóż okazało się, że możliwych ustawień tarczy jest nie trzy a pięć, w dodatku różne tarcze mają różne kolory.

Na szczęście dodatkowe ustawienia nie wymagały praktycznie żadnych zmian w programie - wcześniej jakby coś mnie tknęło i poprawiłem w funkcji wyświetlania LED prog(4) na prog(9). Tak, że wystarczyło dodać dodatkowe linijki do tablicy tarczaTable:

const SemaProg tarczaTable[16]={
    {0},
    {SEMLED(TAR_RED,0,0),"Ms1"},
    {SEMLED(TAR_WHI,0,0),"Ms2"},
    {SEMLED(TAR_RED,0,TAR_RED), "Ms3"}, // nowy
    {SEMLED(0,TAR_WHI,TAR_WHI), "Ms4"}, // nowy
    {0},{0},{0},{0},{0},
    {SEMLED(TAR_RED,TAR_WHI,TAR_WHI),"Sz"}
};

Dodanie informacji o kolorach wymagało jednak więcej zmian.

Przede wszystkim w klasie Semafor należało uwzględnić dodatkową zmienną, przechowującą kolory danej tarczy:

    private:
        uint16_t shColor[2];

W związku z tym w konstruktorze klasy doszły dwa parametry:

    // fragment klasy
    public:
    Semafor(const SemaProg *program, uint8_t whereto, const char *name,
        uint16_t shColor1=0, uint16_t shColor2=0);

    // fragment konstruktora

Semafor::Semafor(const SemaProg *program, uint8_t whereto, const char *name,
    uint16_t shColor1, uint16_t shColor2)
{
    shColor[0]=shColor1;
    shColor[1]=shColor2;


Trzeba było jeszcze przewidzieć metodę klasy udostępniającą owe kolory:

    // fragment klasy Semafor
    const uint16_t *shColors() {return shColor;};

Przy okazji stwierdziłem, że odwzorowanie kolorów na ekranie TFT jest takie raczej średnie, postanowiłem więc zdefiniować podstawowe kolory nieco inaczej niż typowe RGB. Dlatego makra RGB565 i GRAY565 przeszły do Makieta.h, a przy okazji wstawiłem tam definicje kolorów:

#define RGB565(r,g,b) ((((r) & 0b11111000) << 8) | (((g) & 0b11111100) << 3) | ((b) >> 3))
#define GRAY565(n) RGB565(n,n,n)
// kolory LED na ekranie ustawień semaforów
#define CLED_WHITE 0xffff
#define CLED_GREEN RGB565(0,200,0)
#define CLED_RED RGB565(255,0,0)
#define CLED_BLUE RGB565(100,100,255)
#define CLED_ORA RGB565(255,200,0)

Teraz już mogłem przygotować tablice inicjalizacji semaforów - oto jej fragment:

Semafor semafor[]={
    ...
     // tarcza pierwsza kolory domyślne
    Semafor(tarczaTable,WHERETO(0,5),"Tz"),
     // tarcza druga niebieski-biały
    Semafor(tarczaTable,WHERETO(1,5),"Tm",CLED_BLUE,CLED_WHITE),
     // tarcza trzecia zielony - pomarańczowy
    Semafor(tarczaTable,WHERETO(2,5),"To",CLED_GREEN,CLED_ORA),
    // tarcza czwarta  czerwony - zielony
    Semafor(tarczaTable,EXTRSEM(0,1),"SBL",CLED_RED, CLED_GREEN) 
};

Pozostało jeszcze zmienić wyświetlanie kolorów LED na ekranie ustawień semaforów. Oto cała funkcja po zmianach:

#define SEMAPIC_W 30
#define SEMAPIC_H 38


static void drawTSemaLed(int n)
{
    static const uint16_t semaColor[]={
        CLED_GREEN,CLED_ORA,
        CLED_RED,CLED_ORA,CLED_WHITE};
    static const uint16_t shlColor[]={CLED_RED,CLED_WHITE};
    
    int x=semaxy[2*n] * 8;
    int y=semaxy[2*n+1] * 8;
    Semafor *s=&semafor[n];
    const uint16_t *scolor;
    uint8_t nled;
    if (s->prog(9)) {   // dla semafora
        scolor=semaColor;
        nled=5;
    }
    else {  // dla tarczy
        scolor = s->shColors();
        // domyślny dla zapominalskich
        if (!scolor[0]) scolor=shlColor;
        nled=2;
    }
    uint16_t led[2]={0,0};
    int aled=0,i;
    uint8_t mask=s->get();
    oldSemaLed[n]=mask;
    uint8_t lmask=s->getcmd();
    if (s->prog(9)) { 
        // jeśli to semafor, trzeba wybrać diody do wyświetlenia
        for (i=0; i < nled && aled < 2; i++) {
            if (lmask & (1<<i)) led[aled++]= (mask & (1<<i)) ? scolor[i] : 0;
        }
    }
    else {
        // jeśli to tarcza, trzeba wyświewtlić obie diody na ich miejscach
        for (i=0; i < nled; i++) {
            if (lmask & (1<<i)) led[i]= (mask & (1<<i)) ? scolor[i] : 0;
        }
    }
    for (i=0; i<2;i++) {
        tft.fillRect(x-4,y+1,3,12,led[0]);
        tft.fillRect(x-4,y+18,3,12,led[1]);
    }
}

Teraz po skompilowaniu działa wszystko jak trzeba.

I tu uwaga do kolegi wątkotwórcy: nie jestem pewien czy wszystko jest zgodne z tym czego oczekujesz. Jeśli istnieją różne typy tarcz z różnymi ustawieniami (np. jeden typ ma tylko trzy ustawienia, drugi pięć a trzeci trzy ale inne) to program bez żadnych zmian jest w stanie to obsłużyć, tylko muszę dokładnie wiedzieć jak to ma działać.

Czekam na informacje, a kod jak zwykle w załączniku: kolejpan14.zip

Link do komentarza
Share on other sites

Coś ten koniec jakoś dalej ucieka 🙂

Dziś nie będzie nowej wersji - za to pokażę, jak można zaimplementować w programie kilka różnych typów tarcz.

Do tej pory program operował tylko dwoma typami sygnalizatorów: semafor (pięć lamp, ustalone kolory) i tarcza (dwie lampy, ustalone kolory). Okazało się, że to za mało: istnieje kilka różnych typów tarcz z różnymi zestawami kolorów, przy czym mało że kombinacje owych kolorów są różne dla każdego typu, to jeszcze inaczej się nazywają. Czyżby kolejna zmiana w programie?

Ma szczęście nie!

Klasa Semafor jako jeden z parametrów konstruktora przyjmuje tablicę kombinacji zapalonych lamp i nazwy owych kombinacji. Zamiast używać jednej wspólnej tabelki, można po prostu użyć kilku różnych!

Przede wszystkim jednak aby uniknąć niejednoznaczności, zamiast kolorów zdefiniuję po prostu wartości górna-dolna lampa dla tarczy:

#define TAR_UP 1 // górna lampa
#define TAR_LO 2 // dolna lampa

Teraz muszę zdefiniować tablice dla wszystkich rodzajów tarcz (mam informację od kol. @prezesedi że jest ich cztery):

// Tarcza ostrzegawcza
const SemaProg tarczaTable_TO[16] = {
    {0},
    {SEMLED(0,TAR_LO,0),"Os1"},
    {SEMLED(TAR_UP,0,0),"Os2"},
    {SEMLED(TAR_UP,0,TAR_UP), "Os3"},
    {SEMLED(0,TAR_LO,TAR_LO), "Os4"},
};

// Tarcza manewrowa
const SemaProg tarczaTable_TM[16] = {
    {0},
    {SEMLED(TAR_UP,0,0),"Os1"},
    {SEMLED(TAR_LO,0,0),"Os2"},
};

// Tarcza zaporowa
const SemaProg tarczaTable_TZ[16]={
    {0},
    {SEMLED(TAR_UP,0,0),"Ms1"},
    {SEMLED(TAR_LO,0,0),"Ms2"},
    {SEMLED(TAR_UP,TAR_LO,TAR_LO),"Sz"}
7};

// Samoczynna blokada
const SemaProg tarczaTable_SBL[16]={
    {0},
    {SEMLED(TAR_UP,0,0),"S1"},
    {SEMLED(TAR_LO,0,0),"S2"},
};
    
// a tego nie będę używać
#if 0
const SemaProg tarczaTable[16]={
    {0},
    {SEMLED(TAR_UP,0,0),"Ms1"},
    {SEMLED(TAR_LO,0,0),"Ms2"},
    {SEMLED(TAR_UP,0,TAR_UP), "Ms3"},
    {SEMLED(0,TAR_LO,TAR_LO), "Ms4"},
    {0},{0},{0},{0},{0},
    {SEMLED(TAR_UP,TAR_LO,TAR_LO),"Sz"}
};

#endif

Pozostaje jeszcze przyporządkowanie kolorów wyświetlanych na ekranie:

Semafor semafor[]={
    // tu przykładowe dane semaforów

    Semafor(semaTable,WHERETO(0,0),"Sem1"), // semafor pierwszy piny P0..P4
    Semafor(semaTable,WHERETO(1,0),"Sem2"), // semafor drugi piny P10..P14
    Semafor(semaTable,WHERETO(2,0),"Sem3"), // semafor trzeci piny P0..P4
    Semafor(semaTable,EXTRSEM(0,0),"Sem4"), // semafor czwarty MAC 0x20 indeks 0

    // tarcza zaporowa, kolory czerwony-biały (domyślne)
    Semafor(tarczaTable_TZ,WHERETO(0,5),"Tz"),
    // tarcza manewrowa, kolory niebieski-biały
    Semafor(tarczaTable_TM,WHERETO(1,5),"Tm",CLED_BLUE,CLED_WHITE),
    // tarcza ostrzegawcza, kolory zielony - pomarańczowy
    Semafor(tarczaTable_TO,WHERETO(2,5),"To",CLED_GREEN,CLED_ORA),
    // samoczynna blokada, kolory czerwony - zielony
    Semafor(tarczaTable_SBL,EXTRSEM(0,1),"SBL",CLED_RED, CLED_GREEN) 
};

Jest to oczywiście przykład - zastosowałem tu cztery różne tarcze, ale oczywistym jest, że można zastosować różne kombinacje. W podobny sposób można postąpić z semaforami, tam jednak nie ma możliwości zmiany kolorów wyświetlanych na ekranie (co nie powinno stanowić większej trudności, jako że cztero- i trzylampowe semafory nie wprowadzają nowych kolorów). Pamiętać jednak należy o ograniczeniach programu (projektowanego przecież dla konkretnej makiety):

  • Maksymalna ilość podłączonych bezpośrednio sygnalizatorów (4 semafory i 4 tarcze) może być zwiększona, jednak trzeba do tego zmodyfikować program (większa ilość ekspanderów PCF8575);
  • Maksymalna ilość połączonych urządzeń poprzez esp-now jest również ograniczona. Standardowo jest to 7 (chociaż nie jestem pewien, czy Arduinowy kompilat esp-idf nie dopuszcza maksymalnej sprzętowej ilości, czyli 10). Ponieważ odbiornik na ESP8266 może obsługiwać semafor i tarczę lub trzy tarcze, nie powinno stanowić to specjalnego ograniczenia.
  • Istniejący sposób wyboru semafora do ustawienia pozwala na wybranie jednego z dziesięciu za pomocą klawiatury. Zwiększenie ilości semaforów może wymagać zwiększenia rozmiaru tablicy touchArea powyżej 16 (nie mniej niż 4 + ilość semaforów).

Mam nadzieję że to wszystko... ale jak na początku stwierdziłem, ten koniec cały czas gdzieś ucieka 🙂

W każdym razie zostało jeszcze ok. 400 kB wolnej pamięci flash - a pamięć nieużywana to przecież pamięć zmarnowana...

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

Dzień dobry wszystkim.

Kod już przetestowałem. Pozwoliłem sobie wprowadzić kosmetyczne poprawki w oznaczeniu wyświetlanych sygnałów.

Powinno to wyglądać tak:

// Tarcza ostrzegawcza
const SemaProg tarczaTable_TO[16] = {
    {0},
    {SEMLED(0,TAR_LO,0),"Os1"},
    {SEMLED(TAR_UP,0,0),"Os2"},
    {SEMLED(TAR_UP,0,TAR_UP), "Os3"},
    {SEMLED(0,TAR_LO,TAR_LO), "Os4"},
};

// Tarcza manewrowa
const SemaProg tarczaTable_TM[16] = {
    {0},
    {SEMLED(TAR_UP,0,0),"Ms1"},
    {SEMLED(TAR_LO,0,0),"Ms2"},
};

// Tarcza zaporowa
const SemaProg tarczaTable_TZ[16]={
    {0},
    {SEMLED(TAR_UP,0,0),"S1"},
    {SEMLED(TAR_LO,0,0),"Ms2"},
    {SEMLED(TAR_UP,TAR_LO,TAR_LO),"Sz"},
};

// Samoczynna blokada
const SemaProg tarczaTable_SBL[16]={
    {0},
    {SEMLED(TAR_UP,0,0),"S1"},
    {SEMLED(TAR_LO,0,0),"S2"},
};
    
// a tego nie będę używać
#if 0
const SemaProg tarczaTable[16]={
    {0},
    {SEMLED(TAR_UP,0,0),"Ms1"},
    {SEMLED(TAR_LO,0,0),"Ms2"},
    {SEMLED(TAR_UP,0,TAR_UP), "Ms3"},
    {SEMLED(0,TAR_LO,TAR_LO), "Ms4"},
    {0},{0},{0},{0},{0},
    {SEMLED(TAR_UP,TAR_LO,TAR_LO),"Sz"}
};

#endif

Teraz, to jest to o czym rok temu mogłem jedynie śnić. Że znajdzie się ktoś, kto zechce poświęcić tyle swojego czasu i bawić się czymś, z czego nie będzie korzystać dając jednocześnie inne osobie jakże wielofunkcyjne urządzenie. Jeden powie "zabawka" a inny wykorzysta wszystkie jego możliwości i rozszerzy funkcjonalność tego, co do tej ma.

@ethanak z mojej strony ogromne DZIĘKUJĘ.

Link do komentarza
Share on other sites

Ech ten koniec... zamiast się zbliżać to ucieka 🙂

Przy okazji zabawy z wersjami definicji płytek natknąłem się na niemiłą rzecz: bez ekranu dotykowego nie chciały znikać komunikaty błędu i ekran powitalny. Po prostu w ferworze przenoszenia fragmentów kodu w inne miejsca jakoś te miejsca się niespecjalnie zechciały zgodzić z tym co zaplanowałem 🙂

Na szczęście poprawka jest prosta - dotyczy wyłącznie pliku display.cpp należy w funkcji  getTouchedButton usunąć lub zakomentować kilka linii (poniżej zakomentowane):
 

static int getTouchedButton()
{
    if (!getScreenXY(&scrx, &scry)) {
//        if (touchScreenMode != TSM_SEMA && millis() - lastInputActivity > 20000UL) {
//           drawMainMenu(1);
//           drawTSemas();
//        }
        touchCtl=0;
        lastTouchButton=0;
        return 0;
    }

Analogiczny kawałek kodu należy wstawić do TouchLoop:

void touchLoop()
{
    int m=getTouchKey();
    if (!m && have_calpos) m=getTouchedButton();
    if (!m && touchScreenMode != TSM_SEMA && millis() - lastInputActivity > 20000UL) {
        drawMainMenu(1);
        drawTSemas();
    }
    if (m) realizeKey(m);
    refreshTSema();
}

Po tej operacji wszystko powinno ładnie działać„ nawet w sytuacji, kiedy podłączymy wyświetlacz nie obsługujący dotyku.

Przy okazji stwierdziłem, że interfejs na smartfona wygląda nieco siermiężnie, postanowiłem go nieco podrasować:

smartkieta.thumb.png.0c16291282a3b0cfe0f28e526b2f375d.png

W załączniku fragment kodu - wystarczy podmienić pliki webdata.h i webserver.cpp (folder www zawiera nieskompresowane pliki do obejrzenia sobie): kolpanwww1.zip

Ciekawe, czy to już koniec 🙂

 

Link do komentarza
Share on other sites

Cztery posty ukryłem, ponieważ dyskusja odchodziła od głównego tematu. Przypominam również wszystkim o polityce przyjaznego forum. Z góry dziękuję za zrozumienie. Mam nadzieję, że projekt będzie dalej kontynuowany w przyjaznej atmosferze. Nikt nie ma tu (chyba) złych intencji 🚆

  • Lubię! 2
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.