Skocz do zawartości

Arduino w modelarstwie kolejowym


prezesedi

Pomocna odpowiedź

4 minuty temu, ethanak napisał:

ładowania czy kompilacji?

błąd ładowania na płytkę

5 minut temu, ethanak napisał:

Jakie klawisze wciskałeś? Spróbuj np. A3*

obraz.thumb.png.4c1bb7e49a8ee7f736742fac0415fe4d.png to A3*

 

6 minut temu, ethanak napisał:

Możesz mieć odwrotnie podłączoną klawiaturę - jeśli po wciśnięciu 2 pokaże się 4 odwróć klawiaturę albo zamień #if 1 na #if 0 tam, gdzie jest deklaracja pinów klawiatury.

klawiatura wpięta prawidłowo - pod 3 jest 3 pod 2 jest 2

Link do komentarza
Share on other sites

5 minut temu, prezesedi napisał:

błąd ładowania na płytkę

No to nie ma nic wspólnego ze szkicem.

A po wciśnięciu A3* coś tam miga czyli chyba działa (mogłem pomylić sygnały) na pierwszym semaforze, drugi i obie tarcze palą czerwony.

 

 

Link do komentarza
Share on other sites

5 minut temu, ethanak napisał:

A po wciśnięciu A3* coś tam miga czyli chyba działa (mogłem pomylić sygnały) na pierwszym semaforze, drugi i obie tarcze palą czerwony.

obraz.thumb.png.b44453f2f82567802d4f53d17f7cd54f.png ten sygnał oznacza że pali się dioda 1 i miga dioda 5 (sem1); oraz palą się diody 1 na sem2, tar1 i tar2? Czy dobrze odczytuję?

Jeśli tak, to działa jak powinno - ale gdzieś musiałem Cię wprowadzić w błąd z ustawieniem kolorów (znajdę to i podmienię)

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

No to kontynuujemy - czyli próbujemy rozbudować trochę program.

Poprzednia wersja programu co prawda działała, ale była trochę niewygodna. Klawiatura pozwalała na wprowadzenie nieistniejącego semafora lub nieistniejącego ustawienia. Spróbuję to dzisiaj poprawić.

Przy okazji warto zrobić nieco porządku w kodzie. Zamiast trzymać wszystko w jednym wielkim pliku *.ino, warto rozbić to na kilka plików źródłowych, każdy zawierający konkretne funkcje czy dane. Postanowiłem stworzyć następujące pliki:

  • prezesedi3.ino - główny plik szkicu (jak najkrótszy)
  • semafor.cpp - zawierający wszystko co dotyczy operacji na semaforach
  • getcommand.cpp - zawierający funkcie potrzebne do obsługi pulpitu
  • Makieta.h - plik wspólny dla wszystkich, zawierający potrzebne deklaracje i makra.

Zacznijmy może od pliku semafor.cpp.
I tu krótkie wyjaśnienie: na początku pliku mamy linijkę

#include <Arduino.h>

Dotychczas nie była potrzebna (builder sam dodawał taką linijkę na początek pliku *.ino), ale tym razem piszemy w "czystym" c++, a tu kompilator musi mieć jasno powiedziane, że ma włączyć wszelkie arduinowe wynalazki.

Jak widać, zmienił się nieco konstruktor klasy Semafor. Teraz dostaje nie tylko listę ustawień, ale dodatkowo parametr określający, które bity na PCF-ie będą mależeć do niego. Same tabele ustawień też zawierają dodatkowe dane - mianowicie nazwę ustawienia:

typedef struct SemaProg {
    uint16_t prog;
    const char *name;
} SemaProg;

Przykładowy wiersz w tabeli może wyglądać np. tak:

    {COMBO(0,0,1,1,0,SEM_ORU),"S12"}

To oznacza: zapalone obie ledy pomarańczowe, górna miga, a ustawienie nazywa się "S12" (to będę wykorzystywać przy wyświetlaniu).

Reszta jest podobna do poprzedniej wersji, w kodzie są komentarze więc nie warto się rozpisywać. Oto pełna zawartość pliku semafor.cpp:

#include <Arduino.h>
#include "Makieta.h"

/*
 * Metody klasy Semafor
 * (można z nich zrobić bibliotekę)
 */

 
// inicjalizacja obiektu, jako "typ" podstawiamy
// jedną z tabel programów

Semafor::Semafor(const SemaProg *program, uint8_t shift)
{
    _program=program;
    started=false;
    blinkMask=0;
    ledMask=SEM_NEUTRAL;
    _shift=shift;
}

// ustawienie semafora

void Semafor::set(uint8_t command)
{
    uint8_t cmd=_program[command].prog;
    if (!cmd) return;
    ledMask=cmd & 0x1f;
    int blink = (cmd >>5) & 7;
    blinkMask = blink ? 1<<(blink-1) : 0;
    if (ledMask == SEM_NEUTRAL) started=false;
    else {
        started = true;
        startTime = millis();
    }
}


void Semafor::update()
{
    if (started && millis() - startTime > 60000UL) {
        started=false;
        ledMask=SEM_NEUTRAL;
        blinkMask=0;
    }
    currentLed = ledMask;
    if (blinkMask && (millis() & 512)) currentLed &= ~blinkMask;
}

/*
 * A tutaj dane dla konkretnej makiety
 */

// tabele programów dla semaforów

static const SemaProg semaTable[16]={
    {0},
    {COMBO(1,0,0,0,0,0),"S1"},
    {COMBO(0,1,0,0,0,0),"S2"},
    {COMBO(0,1,0,0,0,SEM_GRN),"S2"},
    {COMBO(0,0,1,0,0,SEM_ORU),"S4"},
    {COMBO(0,0,1,0,0,0),"S5"},
    {COMBO(0,1,0,1,0,0),"S10"},
    {COMBO(0,1,0,1,0,SEM_GRN),"S11"},
    {COMBO(0,0,1,1,0,SEM_ORU),"S12"},
    {COMBO(0,0,1,1,0,0),"S13"},
    {COMBO(1,0,0,0,1,SEM_WHI),"Sz"}
};

// to samo dla tarcz

const SemaProg tarczaTable[16]={
    {0},
    {TARCZA(1,0,0),"Ms1"},
    {TARCZA(0,1,0),"Ms2"},
    {0},{0},{0},{0},{0},{0},{0},
    {TARCZA(1,1,TAR_WHI),"Sz"}
};


// teraz tablica obiektów klasy Semafor: dwa semafory i dwie tarcze

static Semafor semafor[]={
    Semafor(semaTable,0),
    Semafor(semaTable,5),
    Semafor(tarczaTable,10),
    Semafor(tarczaTable,12)
};

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
};

Następna część to plik odpowiadający za pobieranie informacji od operatora. Widać tu kilka zmian:

  • funkcja nie pozwala na wprowadzenie nieistniejącego semafora
  • funkcja nie pozwala na wprowadzenie nieistniejącego ustawienia

Można to sprawdzić (oczywiście po skompilowaniu i załadowaniu do płytk): program pozwoli tylko na wprowadzenie semaforów od A do D (zgodnie z mapą semaforów), a np. przy semaforze C (tarcza) pozwoli tylko na programy 1, 2 i 0.

Dodatkowo na monitorze serial mamy teraz nazwę ustawienia (podobno zgodną z tym co rozumieją kolejarze) zamiast jakiegoś numerka.

A oto kod:

#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()
{
    static const char *sema2c=" 1234567890ABCD ";

    // tu powinna być obsługa wyświetlacza ale mi się nie chciało
    if (!kbdStatus) {
        Serial.println("--");
    }
    else if (kbdStatus == KBD_SEMA) {
        Serial.print("SEMA ");
        Serial.println(sema2c[kbdSema]);
    }
    else {
        Serial.print("SEMA ");
        Serial.print(sema2c[kbdSema]);
        Serial.print(" PROG ");
        const char *name=semafor[semaforMap[kbdSema]-1].pgname(kbdProg);
        if (name) Serial.println(name);
        else Serial.println(sema2c[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;
}

Oczywiście, aby to wszystko działało, funkcje muszą mieć jakąś minimalną wiedzę o otaczającym świecie. Wszystkie te informacje zawarte są teraz w oddzielnym pliku Makieta.h i są dostępne dla wszystkich zainteresowanych:

#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 shift);
    void set(uint8_t command);
    void update();
    
    // pobranie aktualnie świecących LED
    uint8_t get() {return currentLed;};

    // przesunięcie dla skonstruowania danych dk PCF-a
    uint8_t shift() {return _shift;};

    // 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;};
    
    private:
    const struct SemaProg *_program;
    uint8_t _shift;
    uint8_t ledMask;
    uint8_t blinkMask;
    uint8_t currentLed;
    bool started;
    uint32_t startTime;
};

#define SEM_RED 1
#define SEM_GRN 2
#define SEM_ORU 3
#define SEM_ORD 4
#define SEM_WHI 5

#define TAR_RED 1
#define TAR_WHI 2

// makra do inicjalizacji tabel
// pięć młodszych bitów to zapalane ledy, trzy starsze to numer
// migającej ledy (1 do 5)

#define COMBO(a,b,c,d,e,blink) \
    (((a)?1:0) | ((b)?2:0) | ((c)?4:0) | ((d)?8:0) | ((e)?16:0) | ((blink)<<5))

#define TARCZA(a,b,blink) COMBO(a,b,0,0,0,blink)

// makra do wyciągnięcia numeru semafora

#define SEM_NUM(c) (((c)>>8)-1)
#define SEM_CMD(c) ((c) & 0xff)

// neutral - pali się tylko czerwona

#define SEM_NEUTRAL 1

extern const int CNT_SEMA;

extern Semafor semafor[];
extern int8_t semaforMap[16];
extern uint16_t getCommand();

#endif

Możemy teraz zobaczyć jak nam się uprościł główny plik ino:

#include <Keypad.h>
#include "Makieta.h"

/*
 * Emulacja pojedynczego PCF8575
 */

 void sendToPcf(uint16_t port)
 {
    char buf[32],*c;
    int i;
    c=buf;
    for (i=0;i<5;i++) *c++= (port & (1<<i))?'*':'.';
    *c++=' ';
    for (i=5;i<10;i++) *c++= (port & (1<<i))?'*':'.';
    *c++=' ';
    for (i=10;i<12;i++) *c++= (port & (1<<i))?'*':'.';
    *c++=' ';
    for (i=12;i<14;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].shift();
    }
    if (port != oldport) {
        oldport=port;
        sendToPcf(port);
    }
}

// no i program główny

void setup()
{
    Serial.begin(115200);
}

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();
}

To oczywiście jeszcze nie koniec. Warto usunąć całą wiedzę o jakichśtam semaforach i pcf-ach z głównego pliku ino - ale o tym będzie w następnej części.

No i kompletny kod:prezesedi3.zip

Chętnych (np. kolegów @farmaceuta i @SOYER) zapraszam do poczytania kodu, przeanalizowania zmian i zadawania pytań (mogą być głupie, nie obrażę się). A kolegę @prezesedi prosiłbym o wrzucenie tego na swoje Arduino i pobawienie się - może coś jeszcze wymyślisz?
 

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

23 minuty temu, ethanak napisał:

A kolegę @prezesedi prosiłbym o wrzucenie tego na swoje Arduino i pobawienie się

Wrzucone, sprawdzone. Obecnie wygląda tak:

obraz.thumb.png.4e27e3db3e1377e5c2863954a91d005c.png

Pisałem wczoraj o złym usytuowaniu kolorów, a mianowicie od góry kolejność jest następująca:

SEM
zielony
pomarańczowy
czerwony
pomarańczowy
biały

TAR
czerwony/niebieski
biały

Czy i kiedy mogę to podmienić, by Ci nie namieszać?

Link do komentarza
Share on other sites

Podmienię w następnej wersji - w sumie to kwestia tylko wpięcia ledów w odpowiednie piny pcf-a, ale jak będą kolejno to będzie lepiej 🙂

Tam zresztą trzeba ustawić nie tylko bity kolorów, ale wartość NEUTRAL, a to w którejś następnej wersji będzie rozwiązane inaczej (możliwość wyświetlenia stanu wszystkich semaforów na wyświetlaczu OLED - ale do tego będzie potrzebny ESP32, Uno ma za mało pamięci a ESP8266 za mało pinów).

Zresztę w tarczy jest chyba dobrze?

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

(edytowany)

Mam ESP-Wroom32 pod ręką - można użyć w każdej chwili

9 minut temu, ethanak napisał:

Zresztę w tarczy jest chyba dobrze?

Tak, jest dobrze

Co do wyświetlaczy OLED to na tę chwilę mam 0,96"  oraz 0,91"

Edytowano przez prezesedi
Link do komentarza
Share on other sites

4 minuty temu, prezesedi napisał:

Co do wyświetlaczy OLED to na tę chwilę mam 0,96"  oraz 0,91"

Zobaczę co się da zrobić - może mi łyknie połówkę wyświetlacza (czyli 128x64 działający jako 128x32). Ale to już nie dzisiaj. A w ESP32 raczej nie byłoby problemu... nie masz żadnego luzem?

 

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

8 minut temu, prezesedi napisał:

mam właśnie luzem ESP32 do wykorzystania

Sorki - cały dzień przed kompem i zaraz mi oczy wypadną, nie doczytałem.

No to następna może nie, ale kolejna z wyściwtlaczem już na ESP32.

 

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

@prezesedi jedno pytanie zanim wrzucę kolejną wersję:

Czy wiesz jak podłączyć pcf-a do Arduino, jak ustawić/sprawdzić adres i jak się do niego podłącza ledy? Wolę się upewnić...

Udało mi się uruchomić mój wyświetlacz 128x64 w trybie 128x32 na Arduino, trochę dziwnie wygląda, ale docelowo i tak będzie ESP32. Na razie kolejna wersja będzie na Arduino (sterowanie rzeczywistum PCF-em plus wyświetlacz dla operatora). Dzisiaj nie zdążę ale i tak  czekam na odpowiedź 🙂

 

Link do komentarza
Share on other sites

Przepraszam, że odpisuję teraz dopiero, ale musiałem wstać przed 4 więc położyłem się wcześniej. Niestety w zastępstwie za chorego, mam wyjazd z pracy na weekend. Kompletnie mi to nie odpowiada ale co zrobić.

6 godzin temu, ethanak napisał:

Czy wiesz jak podłączyć pcf-a do Arduino, jak ustawić/sprawdzić adres i jak się do niego podłącza ledy? Wolę się upewnić...

Czytałem trochę o tym - zero testów.

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.