Kurs C na Atari by Ilmenit 2010-08-20 16:03:04

Jakub "Ilmenit" Dębski napisał:



Zaczynamy

Ten kurs nie będzie uczył języka C, ponieważ w sieci jest olbrzymia liczba materiałów na ten temat. Będzie uczył jak programować w C na Małym Atari. Wykorzystuje on kompilator CC65, który jest wciąż rozwijany, posiada optymalizator, bogatą bibliotekę funkcji standardowych i jest całkiem zgodny ze standardem języka C (z pewnymi wyjątkami, o których będzie mowa później).



Kompilator

Strona projektu kompilatora dostępna jest pod adresem cc65.org. Warto się z nią zapoznać, ponieważ znajduje się na niej wiele przydatnych odnośników, w tym do listy mailingowej, w której historii można znaleźć rozwiązanie wielu potencjalnych problemów. Lista ta jest wciąż aktywna. Warto zapoznać się z „Coding Hints” dostępnymi w dokumentacji. Podczas pisania tego tekstu aktualna wersja kompilatora to cc65-2.13.1-1 do pobrania pod tym adresem.

Najprościej przygotować środowisko instalując je z pliku cc65-2.13.1-1.exe z domyślnymi opcjami instalacji (dodanie oczekiwanych przez kompilator zmiennych środowiskowych CC65_INC i CC65_LIB).

Nowe wersje kompilatora są tworzone z uwzględnieniem wstecznej kompatybilności, ale zmianie często ulegają pliki konfiguracyjne dla platform. W przypadku problemów kompilacji w nowszych wersjach związanych z błędami w pliku CFG należy wykonać polecenie "ld65 --dump-config atari" i zobaczyć jakie są różnice w pliku CFG używanym przez Ciebie i oczekiwanym przez kompilator. Uwaga – dla kompilatora ma znaczenie kolejność segmentów definiowana w pliku CFG.



Pierwszy program

Na początek skompilujmy prosty przykład – program typu „hello world". W tym celu przygotowujemy w dowolnym edytorze plik programu. Najwygodniej wykorzystać jednak środowisko zintegrowane, na przykład "Visual Studio" ("Create a New Project" -> "General" -> "Makefile Project", polecenia kompilujące umieścić w plikach .bat lub bezpośrednio w konfiguracji projektu). Plik nazwijmy przykładowo main.c, ponieważ będzie on zawierał funkcję main(), czyli początkową funkcję programu.

#include < stdio.h >

int main(void)
{
for(;;)
puts("Hello Atari!");
return 0;
}


Program ten kompilujemy poleceniem:

cl65 -t atari main.c -o hello.xex

Parametr „–t atari” określa platformę, na jaką kompilujemy, zaś –o plik wynikowy. Otrzymamy po kompilacji plik hello.xex oraz main.o, zawierający informację dla linkera. Polecenie cl65 wywołuje najpierw kompilator (cc65.exe), asembler (ca65.exe) a następnie linker (ld65.exe), tak więc znacznie upraszcza proces kompilacji programu. W wyniku otrzymujemy program wypisujący na ekranie:




Poprawny program

Poprzedni program nie uwzględnia tego, że na początku trzeba zmienić tryb graficzny na tekstowy, ponieważ program mógł zostać uruchomiony w innym trybie. Do zmiany trybu graficznego służy polecenie _graphics(). Żeby go użyć potrzebujemy zarezerwować pamięć ekranu. Wielkość pamięci dla poszczególnych trybów graficznych opisana jest tutaj. Najprościej wykonać polecenie „ld65 --dump-config atari > atari.cfg”, które wytworzy domyślny plik konfiguracji linkera dla platformy Atari. Zgodnie z tabelką potrzebnej pamięci zmieniamy na 1 odpowiednią wartość:

__RESERVED_MEMORY__: value = 1, weak = yes;

Funkcja _graphics jest specyficzna dla platformy Atari, musimy więc dołączyć plik nagłówkowy atari.h. Poprawny program wygląda następująco:

#include < atari.h >
#include < stdio.h >

int main(void)
{
_graphics(0);
for(;;)
puts("Hello Atari!");
return 0;
}


Ponieważ chcemy użyć nowego pliku konfiguracyjnego, program ten kompilujemy poleceniem:

cl65 -C atari.cfg -t atari main.c -o hello.xex

Wynik programu będzie jak w programie poprzednim. Analogicznie możemy ustawić inne tryby graficzne.



Rysujemy ekran gry

Napiszmy prostą grę typu "Arkanoid" działającą w trybie tekstowym. Plansza gry powinna mieć czarne tło i białe kafelki. Zgodnie z mapą pamięci musimy ustalić odpowiednie wartości COLOR1 i COLOR2:

#include < peekpoke.h >

#define COLOR1 0x2C5
#define COLOR2 0x2C6

void set_colors()
{
POKE(COLOR1,0xFF); // kafelki
POKE(COLOR2,0); // tlo
}


Dostęp do pamięci ekranu uzyskujemy odczytując wartość spod adresu 560 (230h), dodając do niego 4, a następnie pod tym adresem jest wskaźnik do pamięci ekranu. Skomplikowane, ale wszystko jest ponownie opisane w mapie pamięci.

unsigned char *video_ptr;
video_ptr=(unsigned char*)(PEEKW( PEEKW(560)+4 ));


Napiszmy teraz funkcję rysującą ekran i jednocześnie ustalającą liczbę cegiełek do zniszczenia. Dla przyspieszenia kodu będziemy operować na zmiennych globalnych.

#define BRICK 29
#define SCREEN_SIZE 40
unsigned char total_tiles;
void show_screen()
{
for (total_tiles=0;total_tiles < 4*SCREEN_SIZE;total_tiles++)
video_ptr[total_tiles]=BRICK;
}


29 to znak '=' według zestawu znaków układu ANTIC, którego opis znajduje się tutaj. Rysujemy również paletkę o długości 6 znaków w 20 linii ekranu:

#define PADDLE_SIZE 6
#define PADDLE_Y 20
#define PADDLE 213
void draw_paddle()
{
register unsigned char s;
for (s=0;s < PADDLE_SIZE;++s)
video_ptr[PADDLE_Y*SCREEN_SIZE+paddle_pos+s]=PADDLE;
}


Po skompilowaniu uzyskaliśmy prosty ekran gry:



Kod:
#include < atari.h >
#include < stdio.h >
#include < peekpoke.h >

unsigned char *video_ptr;
unsigned char total_tiles;
unsigned char paddle_pos=0;

#define SCREEN_SIZE 40
#define PADDLE_Y 20
#define PADDLE_SIZE 6

#define COLOR1 0x2C5
#define COLOR2 0x2C6
// 29 is '=' character in ANTIC character set
#define BRICK 29
#define PADDLE 213

void set_colors()
{
POKE(COLOR1,0xFF); // font color
POKE(COLOR2,0); // background color
}

void show_screen()
{
for (total_tiles=0;total_tiles < 4*SCREEN_SIZE;total_tiles++)
{
video_ptr[total_tiles]=BRICK;
}
}

void draw_paddle()
{
register unsigned char s;
for (s=0;s < PADDLE_SIZE;++s)
video_ptr[PADDLE_Y*SCREEN_SIZE+paddle_pos+s]=PADDLE;
}

int main(void)
{
_graphics(0);
set_colors();

// get screen memory pointer
video_ptr=(unsigned char*)(PEEKW( PEEKW(560)+4 ));
show_screen();

for(;;)
{
draw_paddle();
}
return 0;
}




Obsługa joysticka

W CC65 obsługa joysticka jest prosta. Potrzebujemy dołączyć plik nagłówkowy:

#include < joystick.h >

Zaś joystick instalujemy za pomocą funkcji:

joy_install(&joy_driver);

Standardowo joystick Atari w CC65 jest obsługiwany za pomocą sterownika dostępnego w pliku CC65joyataristd.joy. Nie chcemy go odczytywać z dyskietki, więc musimy go dołączyć do pliku wykonywalnego. Można to zrobić na kilka sposobów. My przerobimy go na kod asemblerowy, który dołączymy do pliku wykonywalnego. Przy okazji pokażemy jak łączyć kod tworzony w asemblerze z kodem języka C. Do przerobienia sterownika na asembler wykorzystamy program „co65“ opisany tutaj. Poleceniem „co65 ataristd.joy“ generujemy plik „ataristd.s“, po czym dodajemy do niego eksportowany do języka C symbol _joy_driver. Wystarczy zrobić to tylko raz i potem dołączać plik „ataristd.s“ do wszystkich projektów, które wykorzystują joystick. Jest on już przygotowany w pliku PiszemyGre4.zip.

Do obsługi joysticka służy funkcja joy_read() oraz JOY_BTN_xxxx, które są opisane w instrukcji i pliku nagłówkowym joystick.h. Funkcję obsługi joysticka dodajemy w głównej pętli gry, gdzie umieszczamy również opóźnienie za pomocą czasomierza CDTMV3 oraz zerujemy tryb przyciągania uwagi ATTRACT. Tak zmodyfikowany kod wygląda następująco:

#include < atari.h >
#include < stdio.h >
#include < peekpoke.h >
#include < joystick.h >

#define SCREEN_SIZE 40
#define PADDLE_SIZE 6
#define PADDLE_Y 20

#define COLOR1 0x2C5
#define COLOR2 0x2C6
#define CDTMV3 0x21C
#define ATTRACT 0x4D

// 29 is '=' character in ANTIC character set
#define BRICK 29
#define PADDLE 213


// Global variables

unsigned char *video_ptr;
unsigned char total_bricks;
unsigned char paddle_pos=0;

// Joystick

extern char joy_driver;
unsigned char joy;

// Helper macros
#define set_char(x,y,a) video_ptr[(x)+(y)*SCREEN_SIZE]=(a);
#define get_char(x,y) video_ptr[(x)+(y)*SCREEN_SIZE]

void set_colors()
{
POKE(COLOR1,0xFF); // font color
POKE(COLOR2,0); // background color
}

void draw_bricks()
{
for (total_bricks=0;total_bricks < 4*SCREEN_SIZE;total_bricks++)
video_ptr[total_bricks]=BRICK;
}

void draw_paddle()
{
register unsigned char s;
for (s=0;s < PADDLE_SIZE;++s)
set_char(s+paddle_pos,PADDLE_Y,PADDLE);
set_char(paddle_pos-1,PADDLE_Y,0);
set_char(paddle_pos+PADDLE_SIZE,PADDLE_Y,0);
}

int main(void)
{
joy_install(&joy_driver);
_graphics(0);
set_colors();

// get screen memory pointer
video_ptr=(unsigned char*)(PEEKW( PEEKW(560)+4 ));
draw_bricks();

paddle_pos=(SCREEN_SIZE-PADDLE_SIZE)/2;

for(;;)
{
// wait a moment and read the joystick state after that
POKE(CDTMV3,3);
while(PEEK(CDTMV3));
POKE(ATTRACT,0); // Disable Attract mode

joy=joy_read(JOY_1);
if (JOY_BTN_LEFT(joy))
{
if (paddle_pos > 0)
--paddle_pos;
}
if (JOY_BTN_RIGHT(joy))
{
if (paddle_pos < SCREEN_SIZE-PADDLE_SIZE)
++paddle_pos;
}
draw_paddle();
}
return 0;
}


Aby dołączyć plik napisany w asemblerze do programu pisanego w C zwyczajnie dołączamy go do skompilowania w linii poleceń:

cl65 -C atari.cfg -t atari main.c ataristd.s -o hello.xex

W efekcie możemy poruszać już paletką:




Zakończenie

Pozostało dodanie obsługi piłki, licznika „żyć” oraz pozytywnego i negatywnego zakończenia gry. W ramach przykładu zrobimy to na tyle prosto, że gra będzie mało grywalna (wygrana prawie niemożliwa), ale to przecież tylko kurs ;).

Do opcji kompilacji warto dodać parametr „–Osir”, który włączy różny rodzaje optymalizacji:

cl65 -Osir -C atari.cfg -t atari main.c ataristd.s -o hello.xex

Z obsługą piłki cały kod naszej gry wygląda następująco:

#include < atari.h >
#include < stdio.h >
#include < peekpoke.h >
#include < joystick.h >

#define SCREEN_SIZE 40
#define PADDLE_SIZE 6
#define PADDLE_Y 20

#define COLOR1 0x2C5
#define COLOR2 0x2C6
#define CDTMV3 0x21C
#define RANDOM PEEK(0xD20A)
// 29 is '=' character in ANTIC character set
#define BRICK 29
#define PADDLE 213
#define BALL 84

// Global variables

unsigned char *video_ptr;
unsigned char total_bricks;
unsigned char paddle_pos=0;
unsigned char ball_x,ball_y;
signed char ball_dx,ball_dy;
unsigned char lives=6;

// Joystick

extern char joy_driver;
unsigned char joy;

// Helper macros
#define set_char(x,y,a) video_ptr[(x)+(y)*SCREEN_SIZE]=(a);
#define get_char(x,y) video_ptr[(x)+(y)*SCREEN_SIZE]

void set_colors()
{
POKE(COLOR1,0xFF); // font color
POKE(COLOR2,0); // background color
}

void draw_bricks()
{
for (total_bricks=0;total_bricks < 4*SCREEN_SIZE;total_bricks++)
video_ptr[total_bricks]=BRICK;
}

void draw_paddle()
{
register unsigned char s;
for (s=0;s < PADDLE_SIZE;++s)
set_char(s+paddle_pos,PADDLE_Y,PADDLE);
set_char(paddle_pos-1,PADDLE_Y,0);
set_char(paddle_pos+PADDLE_SIZE,PADDLE_Y,0);
}

void win()
{
for(;;)
puts("Win!");
}

void next_life()
{
unsigned char i;
--lives;
if (lives==0)
{
for(;;)
puts("Game over");
}
// show available balls
for (i=0;i < lives;++i)
set_char(i,PADDLE_Y+2,BALL);
set_char(i,PADDLE_Y+2,0);

// set_ball_position

ball_x=paddle_pos+3;
ball_y=18;
ball_dy=-1;
ball_dx=RANDOM%2?1:-1;
}


void move_ball()
{
// remove ball
set_char(ball_x,ball_y,0);

// bounce ball on the top of the screen
if (ball_y==0)
ball_dy=1;
else if (ball_y==PADDLE_Y-1) // ball on the line of the paddle
{
// hit the paddle?
if (ball_x > =paddle_pos && ball_x < paddle_pos+PADDLE_SIZE)
ball_dy=-1;
else // ball missed the paddle
{
next_life();
return;
}
}

// bounce the ball on the borders
if (ball_x==0)
ball_dx=1;
else if (ball_x==SCREEN_SIZE-1)
ball_dx=-1;

// change the ball position
ball_x+=ball_dx;
ball_y+=ball_dy;

// ball hit the brick
if (get_char(ball_x,ball_y)==BRICK)
{
if (--total_bricks==0)
win();
ball_dy*=-1;
}

// draw ball
set_char(ball_x,ball_y,BALL);
}

int main(void)
{
joy_install(&joy_driver);
_graphics(0);
set_colors();

// get screen memory pointer
video_ptr=(unsigned char*)(PEEKW( PEEKW(560)+4 ));
draw_bricks();

paddle_pos=(SCREEN_SIZE-PADDLE_SIZE)/2;
next_life();

for(;;)
{
// wait a moment and read the joystick state after that
POKE(CDTMV3,3);
while(PEEK(CDTMV3));
POKE(0x4D,0); // Disable Attract mode

joy=joy_read(JOY_1);
if (JOY_BTN_LEFT(joy))
{
if (paddle_pos > 0)
--paddle_pos;
}
if (JOY_BTN_RIGHT(joy))
{
if (paddle_pos < SCREEN_SIZE-PADDLE_SIZE)
++paddle_pos;
}
draw_paddle();
move_ball();
}
return 0;
}


Co w efekcie daje:



Dp prac nad programami w C może być też przydatny profiler, który napisałem w czasie prac nad grą "His Dark Majesty". Wszystkie powyższe przykłady w postaci listingów, skompilowanych programów oraz kurs w formacie MS Word - dostępne tutaj.
Kaz 2010-08-20 17:34:25

Jedna uwaga do czytajacych. Z powodu ograniczen technicznych systemu nowinek - w listingu nie wszystko jest zapisane tak jak w oryginalnym listingu Ilmenita. Chodzi o to, ze znaki "<" oraz ">", uzywane w C, tutaj zmieniaja formatowanie tekstu. Dlatego jezeli w listingu bylo na przyklad "" to musialem tam powstawiac spacje i jest "< stdio.h >". Rzecz niewielka, ale zwracam na to uwage. Oczywiscie w zalaczniku na koncu wszystkie listingi sa 100% takie, jak je Ilmenit dostarczyl.

Ilmenit rozwaza napisanie drugiej czesci kursu, w ktorej pokaze, jak uzywac w C zestawow fontow, duszkow i jak odtwarzac muzyke i dzwieki z Raster Music Trackera.

insert 2010-08-20 17:52:38

czy jest mozliwe stworzenie profiler'a dla AtariMACx?

Ilmenit 2010-08-20 18:08:59

Tu masz źródła:
http://www.alamak0ta.republika.pl/profiler.html
Ogólnie nie powinno być problemu z dodaniem, jeżeli potrafisz tę wersję skompilować.

Krótki 2010-08-20 18:10:34

W kwestii < i >: sugerowałbym raczej użycie HTML-owych encji < i >, bo Twoje rozwiązanie jest błędogenne - np. zapomniałeś wstawić spacji w linii

for (s=0;s
która przez to jest niewidoczna.

Krótki 2010-08-20 18:13:21

Oho, widzę, że system komentarzy jest zepsuty - miało być:

HTML-owych encji & lt; i & gt;

(usunąć spacje po ampersandzie).

wawrzyn 2010-08-20 18:20:26

Bardzo fajny tutorial. Ale tez dosc dla zaczynajacych. Jezeli ktos zna C i zna cc65, to tyle sam powinien dac rade zrozumiec. Tematyka drugiej czesci, to by byla dopiero gratka ;) Wedlug mnie warto napisac druga czesc. A za ta czesc chcialem bardzo podziekowac!

test 2010-08-20 18:21:49



nosty 2010-08-20 18:58:29

Kaz - moglbys umiescic to w dziale Poradniki zeby nie zaginelo?

Przeszukiwarka serwisu by google dziala kiepsko i juz pare razy przeczesywalem nowinki z ostatnich 2 lat szukajac czegos.

Dzieki.

xeen 2010-08-20 19:36:58

DRU GA CZĘŚĆ! DRU GA CZĘŚĆ!

Kaz 2010-08-20 21:32:50

Krotki - uzycie encji nic niestety nie zmienia, ten system tak ma, ze musza byc spacje i juz. Aczkolwiek dodalem jeszcze tych spacji tam, gdzie przegapilem wczesniej.

Prosze jednak pamietac - najlepiej kompilowac z przykladow podanych na koncu w zalaczniku!

Nosty - jak pojawi sie druga czesc do wrzuce obie razem do poradnikow.

ampersand 2010-08-20 21:50:45

nie ma nowych gierek ?

Kaz 2010-08-20 22:20:28

Jak to nie ma? Przeciez powyzej masz nowego arkanoida :D

beginner 2010-08-20 23:16:37

#define RANDOM PEEK(0xD20A)

co to robi? ta komorka pamieci nijak mi sie ma do zastosowania jej

bob_er 2010-08-20 23:40:58

definiujesz aliasa o nazwie RANDOM dla PEEK(0xD20A).
od tej chwili gdzie wpiszesz RADNOM, preprocesor wstawi PEEKa.

jhusak 2010-08-21 03:08:10

A jak to zrobić nie używając PEEK?

jhusak 2010-08-21 03:11:19

Tak jest to bez makr:

typedef unsigned char byte;
typedef unsigned word;
byte B;
word W;

*(byte*) 0xD800 = 0x12; /* Store a byte to address $D800 */
*(word*) 0xC000 = 0x1234; /* Store a word to address $C000 */
B = *(byte*) 0xD800; /* Read a byte from address $D800 */
W = *(word*) 0xC000; /* Read a word from address $C000 */

jhusak 2010-08-21 03:12:24

A takie są makra:

#define POKE(addr,val) (*(unsigned char*) (addr) = (val))
#define POKEW(addr,val) (*(unsigned*) (addr) = (val))
#define PEEK(addr) (*(unsigned char*) (addr))
#define PEEKW(addr) (*(unsigned*) (addr))

xxl 2010-08-21 10:14:07

powinna powstac seria takich artkow. z przejmnoscia poczytalbym tez o pascalu.

// get screen memory pointer
video_ptr=(unsigned char*)(PEEKW( PEEKW(560)+4 ));

Ilmenit uzywasz systemu operacyjnego, w komorce 560 jest adres display listy, pobierasz adres pamieci ekranu z rozkazu lms display listy a to troche na okolo i wydluza program, proponowalbym adres pamieci ekranu odczytac z komorki 88 i nastepnej (maja jakas swoja nazwe os ale nie pamietam :p)

mono 2010-08-21 10:19:38

DLPTRS i SAVMSC

gorgh 2010-08-21 12:49:55

@ampersand;@Kaz: jest jakaś gierka o nazwie "Kosmonauti"

Kaz 2010-08-21 12:55:54

Masz na mysli ta gre?

http://atari.fandal.cz/detail.php?files_id=6205

To stara produkcja.

w1k 2010-08-21 19:04:12

main.c: error: include file 'stdio.h' not found..
i hate myself, because i feel like retarded person

w1k 2010-08-21 19:06:46

ERROR: CALL TO UNDEFINED FUNCTIONS 'PUTS'

marlo 2010-08-21 20:04:51

Długo na coś takiego czekałem. A tematyka drugiej części kursu zapowiada się genialnie. Myślę, że może być interesująca dla wielu osób, nie tylko dla mnie. Zatem czekam z niecierpliwością i dziękuję Ilmenitowi, za to co już udostępnił.

gorgh 2010-08-21 21:57:53

rozumiem, widziałem ją po prostu na portalu Retro Game Systems pod rokiem 2010, pewnie się kopneli

jhusak 2010-08-22 10:07:57

W cc65 nie znalazłem globalnych definicji rejestrów na początku pamięci. Jest pliczek asminc/atari.inc, ale tylko do asemblera.

cat asminc/atari.inc | sed 's%^;%//% ; s%;(.*)$%% ; s%(^[A-Za-z][A-Za-z_0-9]*)(.*)$%#ifndef 1%#define 12%#endif%; s%=%%; s% $% 0x%' | tr "%" "n" >include/atari_defs.h

Jeśli to powyżej przeszło, to powstaje pliczek atari_defs.h w katalogu include, trzeba go jeszcze przekopiować do odp. katalogu, aby był wydoczny przez kompilator.

jhusak 2010-08-22 10:10:44

sed include/atari_defs.h

Teraz dobrze przeszło?

jhusak 2010-08-22 10:14:22

Nie, z uporem maniaka wycina backslashe. Przed nawiasami () ifndef 1 define 12 powinno być backslash( backslash) baskslash1 backslash2 (backreferences). Ponadto w tr powinno być backslash n - nowa linia. no i jeszcze po ifndef, define jest %, to go zabackslasować. Powinno działać.

Kaz 2010-08-22 14:19:42

Niestety, to nie jest system przewidziany do pokazywania fragmentow kodu.

Polecam otworzyc watek na forum, tam nie ma takich problemow, sa znaczniki przeznaczone specjalnie do prezentowania fragementow kodu.

Axi0mat 2010-08-24 09:38:18

Proste. Czekam na drugi kurs z spritami i muzyka :)

emka 2010-08-31 00:26:05

Bardzo rzadko się odzywam, ale artykuły o cc65 zaciekawiły mnie na tyle żeby spróbować wydajności tego kompilatora. Po kilku testach wygląda on na najlepszy język jaki powstał na małe ATARI, przebija ACTION!a (chociaż walczy dwudziestokilku letnim dziadkiem).
Mam dwa pytania do praktyków.
Jeżeli zmienna typu (unsigned int) jest porównywana ze stałą większą od 32767 kompilator próbuje traktować ją jako long, czy to jest prawidłowe?
Po dołączeniu (# include atari.h) bardzo podoba mi się odwołanie do rejestrów sprzętowych jako do strukury np. ANTIC.dmactl=0;
Czy w celu lepszego zintegrowania kompilatora z systemem, zmienne systemowe nie powinny być zdefiniowane jako np.
#define WARMST *(unsigned char*) (8)
w jakichś includach.
Wyeliminowało by to większość POKE.
pozdrawiam wszystkich

Kaz 2010-08-31 11:29:49

emka - rowniez pozdrawiam i mam pytanie - moglbys zaprezentowac testy wydajnosci?

PS. Najlepiej wrzucac takie uwagi na forum do watku o CC65, wtedy temat zawsze powedruje na gore i bedzie latwiej widoczny, takze dla autora, od ktorego spodziewasz sie odpowiedzi.

Ilmenit 2010-08-31 12:48:33

emka:
Odnośnie typu long - punkt 14 w
http://www.cc65.org/doc/coding.html

Co do POKE - słusznie, można by się ich pozbyć.

Btw, kompilator ma problem z optymalizacją przy dostępie do pamieci przez wskaźniki do stałych w pamięci.
CC65 produkuje znacznie szybszy kod, gdy istnieje symbol np. dla
1)
unsigned char a[255];
for (i=1;i!=0;++i) a[i] = 0x02;

2)
#define a 0x9C68
for (i=1;i!=0;++i) ((unsigned char*)a)[i] = 0x02;

Dostęp do a[i] jest szybszy w przypadku pierwszej wersji. Takie rzeczy wychodzą dopiero, gdy zaczyna się optymalizować kod, ale wtedy trzeba patrzyć co generuje. Ma nawet znaczenie czy w warunku dla if-else damy znak < czy > i ręcznie zmienimy w kodzie kolejność :)
Ale time-critical code jak za starych dobych czasów warto napisać w asm.

emka 2010-08-31 23:27:13

Co do pierwszego problemu to nie chodziło mi o wskaźniki do stałych w pamięci, ale o typowe stałe na przykład jako granicę pętli np.

for (i=1;i<32768;++i)

Podany link każe dodać U po stałej i należy traktować to jako cechę własną kompilatora.
Co do optymalizacji kodu, to podany przykład ma sens tylko dla tablic mniejszych niż 256 i wskaźników mieszczących się w jednym rejestrze (idealne dla PMG). Dla większych tablic oba przykłady dają identyczny kod wynikowy.
Co do udzielania się na forum to małym ATARI mogę zajmować się tylko w wolnych chwilach (niezbyt często) i jakoś nie mam jeszcze konta.

Pozdrawiam wszystkich i wielkie piwo dla Ilmenit (chyba że preferuje inne trunki).

Fosfor 2010-09-01 10:10:57

Witam, mam pytanie. Bardzo zainteresował mnie ten kurs i następne również. Chciałem napisać program, który wypełni standardowy ekran w trybie 8+16 (numeracja Basic'a) punkt po punkcie w celu zbadania szybkości kodu C. Pierwszy problem, który napotkałem to rezerwacja bloku pamięci dla wspomnianego trybu w pliku konfiguracyjnym. Wg dokumentacji CC65 trzeba zarezerwowac 7146. Z wyliczen wynikałoby że raczej 7680 (40x192), tak czy owak mam problemy z uruchomieniem tego trybu i postawieniem punku na ekranie. Prosiłbym o podpowiedź od osób bardziej doświadczonych w temacie. Pozdrawiam:)

Ilmenit 2010-09-01 11:45:56

Słusznie powinno być 7680. A kod:
#include
#include
#include "atari_defs.h"

unsigned char *video_ptr;

int main(void)
{
register unsigned int i;

_graphics(8+16);
video_ptr=(unsigned char *)PEEKW(SAVMSC);
for (;;)
{
for (i=0;i<40*192;++i)
video_ptr[i]=PEEK(RANDOM);
}
return 0;
}

W grach zdecydowanie bardziej warto wykorzystać tryb znakowy z własnym zestawem znaków - znacznie szybsze.

Ilmenit 2010-09-01 12:21:18

Ale jak chodzi o samo wypełnienie pamięci ekranu, to sprawdź szybkość:
memset(video_ptr,PEEK(RANDOM),40*192);

xxl 2010-09-01 14:57:03

> memset(video_ptr,PEEK(RANDOM),40*192);
o wlasnie, ten powinien byc pieknie optymalizowany, moglbys wkleic kod asemblera jaki sie wygeneruje?

Ilmenit 2010-09-01 15:48:23

memset (jak i inne funkcje biblioteki standardowej) jest napisany w asmie.

xxl 2010-09-01 17:31:56

a mozna zobaczyc jej zrodlo w asemblerze? jest gdzies dostepne?

Ilmenit 2010-09-01 18:12:26

Źródła kompilatora są do pobrania z ftp://ftp.musoftware.de/pub/uz/cc65/snapshot/
memset jest w pliku cc65-snapshot-2.13.9.20100824libsrccommonmemset.s