Kompilowanie kodu do uruchomienia z zewnętrznej pamięci RAM


13

Rozważam projekty minimalistycznego systemu gier opartego na PIC18F85J5. Częścią mojego projektu jest to, że gry można ładować z karty SD bez konieczności przeprogramowywania układu lub flashowania pamięci programu. Wybrałem ten układ, ponieważ ma interfejs pamięci zewnętrznej, który pozwoli mi uruchamiać kod z zewnętrznej pamięci SRAM.

Podstawową ideą jest to, że wewnętrzna pamięć programu będzie zawierała interfejs do przeglądania karty SD, a gdy użytkownik wybierze program, skopiuje plik hex z karty SD do zewnętrznego RAM, a następnie przeskoczy wykonanie do zewnętrznego RAM RAM .

Wewnętrzna pamięć programu będzie także zawierać różne biblioteki grafiki, danych wejściowych kontrolera i innych różnych narzędzi.

Jestem przekonany, że wiem, jak sprawić, by wewnętrzne części oprogramowania układowego działały poprawnie. Problemem jest tworzenie programów do uruchamiania z zewnętrznej pamięci RAM. Nie wygląda to tak samo, jak celowanie w zwykłe zdjęcie i musi być świadomy funkcji bibliotecznych dostępnych w pamięci wewnętrznej, ale nie należy ich ponownej kompilacji, a jedynie łącza do nich. Musi także zacząć używać adresów tuż po 32k wewnętrznej pamięci flash, a nie na zero. Czy istnieje dobry sposób na kompilację programu przy użyciu tego rodzaju ograniczeń?

Korzystam z MPLab IDE, ale nie jestem zbytnio obeznany z tym rodzajem dostosowywania.


Jedno z najlepszych pytań, jakie tu widziałem ... Nie mogę się doczekać, aby usłyszeć pomysły i odpowiedzi.
Jon L

Kilka interesujących alternatywnych podejść omówiono w elektronice.stackexchange.com/questions/5386/…
Toby Jaffey

Widziałem to, ale najczęściej polecają pisanie na flash, czego chciałbym uniknąć. Mój projekt sprzętu będzie przypominał rysunek 6 w nocie aplikacyjnej w zaakceptowanej odpowiedzi.
captncraig

Odpowiedzi:


8

Masz dwa osobne problemy:

  1. Budowanie kodu dla zewnętrznego zakresu adresów RAM.

    To jest naprawdę bardzo łatwe. Wszystko, co musisz zrobić, to utworzyć plik linkera, który zawiera tylko zakresy adresów, które ma zajmować kod. Pamiętaj, że musisz nie tylko zarezerwować konkretny zakres adresów pamięci programów dla tych zewnętrznych aplikacji, ale także trochę pamięci RAM. Ta przestrzeń RAM, podobnie jak adresy pamięci programu, musi zostać ustalona i znana. Po prostu udostępniaj tylko te ustalone i znane zakresy adresów do wykorzystania w pliku linkera. Nie zapomnij, aby NIE były dostępne w pliku linkera kodu podstawowego.

    Po załadowaniu nowego kodu aplikacji do pamięci zewnętrznej kod podstawowy musi wiedzieć, jak go wykonać. Najłatwiej jest prawdopodobnie rozpocząć wykonywanie od pierwszej zewnętrznej lokalizacji RAM. Oznacza to, że Twój kod będzie potrzebował jednej sekcji KOD pod tym absolutnym adresem początkowym. Zawiera GOTO po prawej etykiecie w pozostałej części kodu, którą można przenieść.

  2. Łączenie aplikacji z procedurami bibliotecznymi w kodzie podstawowym.

    Nie ma natychmiastowego prostego sposobu na zrobienie tego za pomocą istniejących narzędzi Microchip, ale też nie jest tak źle.

    Dużo większym problemem jest sposób radzenia sobie ze zmianami kodu podstawowego. Uproszczoną strategią jest zbudowanie kodu podstawowego, uruchomienie programu na wynikowym pliku mapy w celu zebrania adresów globalnych, a następnie zapisanie pliku importu z instrukcjami EQU dla wszystkich globalnie zdefiniowanych symboli. Ten plik importu byłby wówczas dołączony do całego kodu aplikacji. Nie ma nic do połączenia, ponieważ kod źródłowy aplikacji zawiera zasadniczo stałe odniesienia do punktów wejścia kodu podstawowego.

    Jest to łatwe i działa, ale zastanów się, co się stanie, gdy zmienisz kod podstawowy. Nawet drobna poprawka błędu może spowodować, że wszystkie adresy będą się przemieszczać, a wtedy cały istniejący kod aplikacji nie będzie dobry i będzie musiał zostać przebudowany. Jeśli nigdy nie planujesz dostarczać aktualizacji kodu podstawowego bez aktualizacji wszystkich aplikacji, być może uda ci się to zrobić, ale myślę, że to zły pomysł.

    Lepszym sposobem jest posiadanie zdefiniowanego obszaru interfejsu pod wybranym stałym znanym adresem w kodzie podstawowym. Dla każdego podprogramu, który może wywoływać kod aplikacji, istnieje jedno GOTO. Te GOTO byłyby umieszczane pod ustalonymi znanymi adresami, a aplikacje zewnętrzne dzwoniłyby tylko do tych lokalizacji, które następnie przeskakiwałyby tam, gdzie podprogram faktycznie znalazł się w tej wersji kodu podstawowego. To kosztuje 2 słowa pamięci programu na eksportowany podprogram i dwa dodatkowe cykle w czasie wykonywania, ale myślę, że warto.

    Aby to zrobić, musisz zautomatyzować proces generowania GOTO i wynikowego pliku eksportu, który aplikacje zewnętrzne zaimportują w celu uzyskania adresów podprogramu (a właściwie przekierowania GOTO). Być może będziesz w stanie poradzić sobie z pewnym sprytnym użyciem makr MPASM, ale gdybym to robił, zdecydowanie użyłbym mojego preprocesora, ponieważ może on zapisywać do zewnętrznego pliku w czasie wstępnego przetwarzania. Możesz napisać makro preprocesora, aby każdy readresator mógł być zdefiniowany przez pojedynczy wiersz kodu źródłowego. Makro robi wszystkie nieprzyjemne rzeczy pod maską, tj. Generuje GOTO, zewnętrzne odniesienie do rzeczywistej procedury docelowej i dodaje odpowiednią linię do pliku eksportu ze znanym stałym adresem tej procedury, wszystkie o odpowiednich nazwach. Być może makro tworzy po prostu kilka zmiennych preprocesora o regularnych nazwach (tak jak tablica rozszerzalna w czasie wykonywania), a następnie plik eksportu jest zapisywany raz po wszystkich wywołaniach makr. Jedną z wielu rzeczy, które mój preprocesor może zrobić, czego nie mogą zrobić makra MPASM, jest manipulacja ciągiem znaków w celu zbudowania nowych nazw symboli z innych nazw.

    Mój preprocesor i kilka innych powiązanych rzeczy jest dostępnych za darmo na stronie www.embedinc.com/pic/dload.htm .


Naprawdę podoba mi się pomysł stałego stołu skokowego. Mógłbym zacząć od końca pamięci flash i mieć ustalone lokalizacje dla każdego wywołania systemowego. Mógłbym nawet uciec od ręcznie utrzymywanego pliku nagłówka z adresami wszystkich podprogramów, jeśli nie mogę zrozumieć całego tego voodoo preprocesora, który opisujesz.
captncraig

5

Opcja 1: Języki interpretowane

To nie odpowiada bezpośrednio na pytanie (które jest doskonałym pytaniem, BTW i mam nadzieję wyciągnąć naukę z odpowiedzi, która odnosi się do niego bezpośrednio), ale jest bardzo częste podczas wykonywania projektów, które mogą ładować programy zewnętrzne w celu pisania programów zewnętrznych język interpretowany. Jeśli zasoby są napięte (które będą na tym procesorze, czy myślałeś o użyciu do tego PIC32 lub małego procesora ARM?), Często ogranicza się język do podzbioru pełnej specyfikacji. Jeszcze niżej znajdują się języki specyficzne dla domeny, które robią tylko kilka rzeczy.

Na przykład projekt elua jest przykładem języka interpretowanego o niskim poziomie zasobów (64 kB RAM). Możesz zmniejszyć to do 32 KB pamięci RAM, jeśli usuniesz niektóre funkcje (Uwaga: nie będzie działać na bieżącym procesorze, który jest architekturą 8-bitową. Korzystanie z zewnętrznej pamięci RAM prawdopodobnie będzie zbyt wolne dla grafiki). Zapewnia szybki, elastyczny język, w którym nowi użytkownicy mogliby z łatwością programować gry, jeśli zapewnią minimalny interfejs API. Istnieje wiele dokumentacji dostępnych dla tego języka online. Są inne języki (jak Forth i Basic), których można użyć w podobny sposób, ale myślę, że Lua jest obecnie najlepszą opcją.

W podobny sposób możesz stworzyć własny język specyficzny dla domeny. Musisz podać pełnoprawny interfejs API i zewnętrzną dokumentację, ale jeśli wszystkie gry byłyby podobne, nie byłoby to zbyt trudne.

W każdym razie PIC18 prawdopodobnie nie jest procesorem, którego użyłbym do czegoś, co wymaga niestandardowego programowania / skryptów i grafiki. Być może znasz tę klasę procesorów, ale sugerowałbym, że byłby to dobry czas na użycie czegoś ze sterownikiem ekranu i większą pamięcią.

Opcja 2: Po prostu przeprogramuj całość

Jeżeli jednak jesteś już planuje programowania wszystkie gry się w C, to nie przejmuj się ładuje tylko logikę gry z karty SD. Masz tylko 32kB Flasha do przeprogramowania i możesz łatwo uzyskać kartę microSD o pojemności 4 GB. (Uwaga: większe karty są często SDHC, z którymi trudniej jest się połączyć). Zakładając, że używasz każdego ostatniego bajtu swojego 32 kB, co pozostawia miejsce na karcie SD na 131 072 kopii oprogramowania układowego z dowolną logiką gry, jakiej potrzebujesz.

Istnieje wiele aplikacji do pisania programów ładujących dla PIC, takich jak AN851 . Musisz zaprojektować program ładujący tak, aby zajmował określony region pamięci (prawdopodobnie górną część regionu pamięci, określiłbyś to w linkerze) i określił, że projekty pełnego oprogramowania układowego nie docierają do tego regionu. W przypisie wyjaśniono to bardziej szczegółowo. Wystarczy zamienić „sekcję rozruchową PIC18F452” na „sekcję rozruchową, którą określam w linkerze”, a wszystko to będzie miało sens.

Następnie program ładujący musi tylko pozwolić użytkownikowi wybrać program do uruchomienia z karty SD i skopiować całość. Interfejs użytkownika może oznaczać, że użytkownik musi przytrzymać przycisk, aby przejść do trybu wyboru. Zwykle bootloader sprawdza status tego przycisku po zresetowaniu i, jeśli nie jest przytrzymany, uruchamia się w grze. Jeśli zostanie przytrzymany, użytkownik musi wybrać plik na karcie SD, skopiować program i kontynuować uruchamianie w [nowej] grze.

To jest moje obecne zalecenie.

Opcja 3: Głęboka magia polegająca na przechowywaniu tylko części pliku hex

Problem z przewidywanym mechanizmem polega na tym, że procesor nie obsługuje interfejsów API i wywołań funkcji, zajmuje się liczbami - adresami, do których wskaźnik instrukcji może przeskakiwać i oczekiwać, że pojawi się kod wykonujący wywołanie funkcji zgodnie ze specyfikacją API. Jeśli spróbujesz skompilować tylko część programu, linker nie będzie wiedział, co zrobić, gdy zadzwonisz check_button_status()lub toggle_led(). Możesz wiedzieć, że funkcje te istnieją w pliku szesnastkowym procesora, ale musi dokładnie wiedzieć, pod jakim adresem się znajdują.

Linker już dzieli twój kod na wiele sekcji; Teoretycznie można przełamać to na dodatkowe sekcje z niektórymi -sectioni #pragmazaklęć. Nigdy tego nie robiłem i nie wiem jak. Dopóki powyższe dwie metody mnie nie zawiodą (lub ktoś nie opublikuje tutaj niesamowitej odpowiedzi), prawdopodobnie nie nauczę się tego mechanizmu, więc nie mogę cię tego nauczyć.


Mój sprzeciw wobec numeru 2 polega na tym, że pamięć flash ma ograniczoną liczbę cykli kasowania w czasie życia układu. Nie chcę używać mocniejszego mcu, ponieważ wybieram 8-bitowy minimalizm.
captncraig

@CMP - Ma co najmniej 10 000 cykli kasowania. Jeśli ktoś codziennie gra w inną grę, potrwa ona do roku 2039. Flash prawie na pewno przetrwa resztę obwodu. Nie sądzę, że powinieneś się tym martwić, chyba że będzie to arkada, w którą będziesz grać dziesiątki razy każdego dnia.
Kevin Vermeer

1
Po drugie, 8-bitowy minimalizm może wyglądać fajnie, ale jest do bani w programowaniu. Znacznie łatwiej jest uzyskać solidny mikrokontroler i sprawić, by wyglądał retro, niż być zmuszonym, aby wyglądał retro, ponieważ przekraczasz granice swojego procesora.
Kevin Vermeer

1
Oba bardzo uczciwe punkty. Nawet jeśli wybierzesz niską liczbę części, pic32 nie różni się pod względem kosztów ani komponentów zewnętrznych od tego, a jeśli ma 512K wewnętrznej pamięci flash, nawet wygrywa.
captncraig

Wygląda na to, że praktycznym rozwiązaniem byłoby użycie pic32 i napisanie bootloadera w celu ponownego flashowania z karty SD. Trudno byłoby mi ponownie wykorzystać funkcje, których potrzebowałby zarówno bootloader, jak i kod użytkownika, ale tak długo, jak trzymam go w 12k flashie rozruchowym, powinien dać kodowi użytkownika cały układ i mogą one zawierać dowolne biblioteki u źródła poziom.
captncraig
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.