Jump to content
IGNORED

GCC for the TI


insomnia

Recommended Posts

40 minutes ago, Tursi said:

What I find works well, is any time you are waiting for the user (title page, statistics page, any input at all), just call random() as part of the loop.

That is a very good idea: easy, workable and does the trick indeed (tried, and get different dice throw sequences now each time). Thanks!

Edited by xahmol
Link to comment
Share on other sites

1 hour ago, Asmusr said:

You can also use the word at >83c0 as seed.

I quit using that one early on, because I had gotten so practiced in powering up, hitting space and then '2' for Editor/Assembler that I actually /did/ get the same seed repeatedly. ;)

 

  • Haha 2
Link to comment
Share on other sites

Another question:

 

On compiling this line in my code:

if(playerpos[playernumber][pawnnumber][0]==0 && (playerpos[playernumber][pawnnumber][1]%10)== 0)

I get this error:

/home/xahmol/tms9900gcc/bin/tms9900-gcc -c main.c -std=c99 -O2 --save-temp -I/home/xahmol/libti99 -o main.o
main.c: In function ‘pawnerase’:
main.c:855: internal compiler error: output_operand: invalid expression as operand
Please submit a full bug report,
with preprocessed source if appropriate.
See <http://gcc.gnu.org/bugs.html> for instructions.
make: *** [Makefile:65: main.o] Error 1

If I change the modulo operator % to / it does compile (but of course it functions not as intended). so it seems the % operator is to blame:

if(playerpos[playernumber][pawnnumber][0]==0 && (playerpos[playernumber][pawnnumber][1]/10)== 0)

Line does compile as intended in CC65.

Is this a compiler bug? And if yes, how and where to report?

 

Or does % not work on a char on this target? Declaration of the variable is unsigned char playerpos[4][4][2];.

 

For now did this as workaround which does compile and work:

boardpos = playerpos[playernumber][pawnnumber][1];
if(playerpos[playernumber][pawnnumber][0]==0 && (boardpos==0 || boardpos==10 || boardpos==20 || boardpos==30 ))

But of course curious why the first code does not compile on this target.
 

Edited by xahmol
Link to comment
Share on other sites

Yes, compiler bug. Follow the instructions in the error message. ;)

 

In the meantime, using an int instead of char will probably work, if you can. char handling on the 9900 is tricky for a compiler and there have been other issues there.

 

  • Like 1
Link to comment
Share on other sites

17 minutes ago, Tursi said:

In the meantime, using an int instead of char will probably work, if you can. char handling on the 9900 is tricky for a compiler and there have been other issues there.

 

Using int in stead of char would not be big issue, but have it working now with the workaround, so any reason to still change it?

 

Structurally used char instead of int in my source as on CC65 for the 6502 CPUs chars are much faster (and take less memory).

Link to comment
Share on other sites

On 3/20/2021 at 4:24 PM, jedimatt42 said:

The TIPICFG source in the TIPI repo on github uses libti99's DSR routines to read and write.

Question: the DSR read and write routines work fine, so thanks! Can definately use those for config file and save games.

But: I see these routines are for sequential files with records. And those records are zero terminated.

 

I store for example player number in the config file, which can be zero. Actually start the file with it. So at first it was not saving anything. Took me a while to think that that might be because saving the record is zero terminated, so it terminates right at the first byte. Solved for now by adding 1 to everything (as max value of everything I want to save will not be over 128 anyway).

 

But I would also like to be able to load for example music to be able to shuffle through more than one music track without the need to fit all in memory at the same time and just have the active track in memory. Obviously for that record based, and especially zero terminated, is not very handy (can work around it if I really want by converting the files from pure binary to hexadecimal codes in ASCII, but that probably is slow and cumbersome, so best avoided if needed).
I see the TIPICG source uses DSR_READ and DSR_WRITE, where also DSR_SAVE and DSR_LOAD are available as types in files.h from Libti99. Tried those, but can not get them working yet. It just saves nothing but a TIFILES header.
 

So:

- is it possible to read/write just plain binary files with the Libti99 lib?

- if yes, any code examples or hints?

 

(VDP file buffer size should not be a contraint by the way. Use set_graphics(0), so relatively much size left in VDP memory for a file buffer of 6-8k)

Edited by xahmol
Link to comment
Share on other sites

The routines in TIPICFG wrap and constrain the routines in libti99 for my convenience... You can do binary data using (simplifying) record format files with INTERNAL FIXED type files... You want to learn the TI file system: http://aa-ti994a.oratronik.de/FuncSpec_for_99_4_Disk_Peripheral_V3_0_03_28_1983.pdf

 

LOAD and SAVE are great for copying a chunk of memory in VDP to or from DISK as single files... they become PROGRAM image files but do not have to contain 'programs'.  They are often used for data in programs like 'Adventure', 'Tunnels of Doom', 'TI Artrist', etc...  I believe you'd use libti99's dsrlnk function, with a something like:

 

struct PAB pab;
// not relavent, but lets initialize all the bits
pab.ScreenOffset = 0; 
pab.Status = 0;
pab.RecordLength = 0;
pab.CharCount = 0;
// the stuff that matters...
pab.OpCode = DSR_LOAD; // or DSR_SAVE
pab.VDPBuffer = 0x3000; // where in VDP you want to load (or save from)
pab.RecordNumber = 0x0400; // use 1K of VDP RAM (size to load or save since we aren't using records)
pab.NameLength = strlen(filename);
pab.pName = filename;

int pab_vdpaddr = 0x2400; // The PAB will be copied from CPU RAM to VDP RAM, and should not overlap with the data buffer, and ideally not with the screen usage... unless of course you want to load a screen... 
int err = dsrlnk(&pab, pab_vdpaddr);
// if !err, then pab.VDPBuffer should contain your data in VDP RAM.

 I haven't tried this :) in this context... 

  • Like 2
  • Thanks 1
Link to comment
Share on other sites

An important rule I've learned over decades of programming is "don't try to be smarter than your tools". If you're using an API that reads and writes strings, then read and write strings, don't decide to drop binary data in there. You've just seen why. Binary data always needs to be paired with a length in C. If there's no length, it's probably NUL terminated.

 

The TI file system itself doesn't care of course, so you can just extend Matt's wrapper to take a length instead of using strlen, and you'll probably be fine. Alternately, if you aren't actually doing records but just want a blob of data, second vote for LOAD/SAVE "program" images - it's the simplest binary blob and also the fastest, since you only need one DSR call. (IE: just "LOAD", not "OPEN/READ/CLOSE").

 

  • Like 2
  • Thanks 1
Link to comment
Share on other sites

Here's another example of LOAD, from my GROMCFG tool (so it is tested). I've editted out some of the irrelevant lines:

 

struct PAB pabdat;

[...]

	pabdat.OpCode = DSR_LOAD;
	pabdat.Status = 0;
	pabdat.VDPBuffer = VDP_BUFFER;	// address of the data buffer in VDP memory
	pabdat.RecordLength = 0;		// size of records. Not used for PROGRAM type. >00 on open means autodetect
	pabdat.CharCount = 0;			// number of bytes read or number of bytes to write (per record)
	pabdat.RecordNumber = 8192+6;	// record number for normal files, available bytes (LOAD or SAVE) for PROGRAM type
	pabdat.ScreenOffset = 0;		// Used in BASIC for screen BIAS. Also returns file status on Status call. (DSR_STATUS_xxx)
	pabdat.NameLength = 0;			// for this implementation only, set to zero to read the length from the string
	pabdat.pName = name;			// for this implementation only, must be a valid C String even if length is set

	// zero the buffer
	vdpmemset(VDP_BUFFER, 0xff, 8192+6);

	if (dsrlnk(&pabdat, VDP_PAB)) {
		erasehelp();
		unsigned char x = vdpreadchar(VDP_PAB+1);	// changes VDP address
		writestring(3, 13, "DSR Error ");
		faster_hexprint(x);							// relies on VDP address from writestring
		waiterror(5);
		return;
	}

For SAVE, the record number is how many bytes to write. For LOAD, the record number is the size of the buffer. If the file is larger than your buffer, you will get an ERROR, you will NOT get a partial load. Something to be aware of. That's where the +6 came from in there... unrelated to what you are doing, GRAM Kracker files could be 8k plus a six byte header, and that's why you see that in my code, as a reminder. ;)

 

  • Like 1
  • Thanks 1
Link to comment
Share on other sites

Thanks! And think I now know what I was doing wrong, was indeed trying to do OPEN/SAVE/CLOSE and OPEN/LOAD/CLOSE instead of just SAVE and LOAD. Also because the Oric Atmos code I am converting actually did need an OPEN and CLOSE also on a binary blob.

Will try this!

(and yes, I am fully guilty on not reading up on TI file systems up front. Wanted to do it quick and dirty and see where it went ;-) So thanks for bearing with me)

Edited by xahmol
Link to comment
Share on other sites

4 hours ago, xahmol said:

Thanks! And think I now know what I was doing wrong, was indeed trying to do OPEN/SAVE/CLOSE and OPEN/LOAD/CLOSE instead of just SAVE and LOAD. Also because the Oric Atmos code I am converting actually did need an OPEN and CLOSE also on a binary blob.

Will try this!

(and yes, I am fully guilty on not reading up on TI file systems up front. Wanted to do it quick and dirty and see where it went ;-) So thanks for bearing with me)

Yeah, TI's file system approach is a little weird, with the strongly typed files. Except for that point, it's very Unix-y which is kind of nice (ie: all devices are files). That LOAD and SAVE are further exceptions is also weird, but it's nice in that it makes them fairly easy to use. ;)

 

  • Like 3
Link to comment
Share on other sites

Thanks a lot! Works great now, changed also my configfile and savegames to binary blobs. My save game system works!

Using this code mostly from your code suggestions:

unsigned char dsr_load(unsigned char* filename, unsigned char* dest, unsigned char numbytes)
{
    /*  Load file.
        Inspired by Jedimatt42 and Tursi
        Source: https://atariage.com/forums/topic/164295-gcc-for-the-ti/page/25/?tab=comments#comment-4786953 */

    struct PAB pab;
    unsigned char ferr;

    // not relavent, but lets initialize all the bits
    pab.ScreenOffset = 0; 
    pab.Status = 0;
    pab.RecordLength = 0;
    pab.CharCount = 0;
    // the stuff that matters...
    pab.OpCode = DSR_LOAD; // DSR_LOAD for LOAD
    pab.VDPBuffer = FBUF; // where in VDP you want to load (or save from)
    pab.RecordNumber = numbytes; // size to load since we aren't using records
    pab.NameLength = strlen(filename);
    pab.pName = filename;
 
    ferr = dsrlnk(&pab, VPAB);
    if(!ferr) { vdpmemread(FBUF, dest, numbytes); }
    return ferr;
}

unsigned char dsr_save(unsigned char* filename, unsigned char* source, unsigned char numbytes)
{
    /*  Save file.
        Inspired by Jedimatt42 and Tursi
        Source: https://atariage.com/forums/topic/164295-gcc-for-the-ti/page/25/?tab=comments#comment-4786953 */

    struct PAB pab;

    // not relavent, but lets initialize all the bits
    pab.ScreenOffset = 0; 
    pab.Status = 0;
    pab.RecordLength = 0;
    pab.CharCount = 0;
    // the stuff that matters...
    pab.OpCode = DSR_SAVE; // DSR_SAVE for SAVE
    pab.VDPBuffer = FBUF; // where in VDP you want to load (or save from)
    pab.RecordNumber = numbytes; // size to save since we aren't using records
    pab.NameLength = strlen(filename);
    pab.pName = filename;
    
    vdpmemcpy(FBUF, source, numbytes);
    return dsrlnk(&pab, VPAB);
}

 

  • Like 5
Link to comment
Share on other sites

Finished a first working and complete version of the game. See the ZIP attached, unzip contents to TIPI root dir.

 

Game is menu driven and controlled by either joystick or cursor keys. Still contemplating if on TI target the cursor keys are handy, as they on TI require two keys pressed (while my other targets have dedicated cursor keys).

 

Things to do:

  • Much more playtesting. While the source logic has been tested on the Oric Atmos, want to be sure with sufficient play testing on TI target as well.
  • Not entirely happy with the music yet. As I did not succeed in doing it via interrupts I included the play next note routine in the wait and getkey loops. Works, but timing gets a little off sometimes when the game is in another loop. Working code examples of doing music with interrupts in a C source are always welcome
  • Add TI-99/4a specific documentation
  • Add credits for the music I borrowed

Gladly hear your feedback and suggestions. Not much room for additions though, as I already encountered before removing some test code that I completely fill the allocated expanded upper memory space. So adding stuff will also cause need to remove or reduce stuff elsewhere.

 

Tested over here in Classic99 and on my original TI-99/4a hardware with sidecar TIPI.

 

Full source code:

https://github.com/xahmol/ludo/tree/main/TI994a
(see upwards tree for the Oric Atmos code and the original Commodore 128 BASIC listing from 1992 that I programmed in my youth and that is the base)

LudoTI994a-v199-20210326-1605.zip

 

Schermafbeelding 2021-03-26 163009.png

Edited by xahmol
  • Like 3
Link to comment
Share on other sites

Anybody having pointers for doing speech in C?

 

I did find assembly code source in the Rock Runner game, but I am really illiterate in TI assembly still coming from 6502.

So of course I can study, but if someone has easily available C code that would be great.

 

EDIT: See separate topic 

 

Edited by xahmol
Link to comment
Share on other sites

This may prove to be difficult without machine language. The problem is that the speech synthesizer is a very slow device; it occupies the bus quite long. Therefore, as explained in the Editor/Assembler manual (p. 349, section 22.1.1), you have to use a delay loop which must be located on the internal 16-bit bus and so wait your time without touching the external bus. This loop may be put in the scratch pad RAM at >8300.

 

It may be possible, but you must somehow weave that piece of machine language into your C code.

  • Like 1
Link to comment
Share on other sites

13 hours ago, xahmol said:

Ok, had hoped it would be as easy as just dropping some LPC binary code from BlueWizard/Python Wizard somewhere ? Will need study then I guess.

 

I've slapped together some routines today: https://github.com/jedimatt42/fcmd/blob/jm42/say-toy/example/gcc/say/say.c  ( while this is a Force Command executable, none of the functions rely on Force Command )

 

void speech_reset(); // send speech reset command
int speech_detect(); // return 0 if no speech rom found
void say_vocab(int phrase_addr); // issue command to say a phrase in the builtin vocabulary
void say_data(int* code_addr, int len); // play speech code from CPU RAM
void speech_wait(); // try, but fail to wait for speech to stop. 

Basically copied / translated from the code in Editor Assembler manual page 357. Includes moving a 'READIT' routine ( I called it 'safe_read' ) into scratchpad ( 16 bit ram ) 

 

TI Tech Pages asserts that the READY line on the CPU is controlled by the speech hardware, so in practice that buffer full code isn't necessary unless you want to interleave other behavior with streaming the speech. 

 

So... on my 4A the say_data routine is working in isolation.

 

When I run:

say_vocab(...)

speech_wait()

speech_reset()

say_data(...)

speech_wait()

 

On the 4A, the say_vocab is cut-off immediately.. seems the waiting for speech doesn't work yet... ( maybe I need a delay before looking at the status, if the status hasn't changed to talking yet... The EA code doesn't do anything special... ) It worked under the classic99 debugger when I stepped through...

 

Anyway, maybe close to useful... 

Edited by jedimatt42
  • Like 2
  • Thanks 1
Link to comment
Share on other sites

Thanks! Tried it, works if I compile your code as seperate program as you said (custom data works, say_vocab is cut of) on real hardware.

 

If I try to use it in my own program though it completely crashes on real hardware. It actually does speak, both the Hello (standard vocabulary)  and the That is right (customn data) part strangly enough. But screen completely gets scrambled and program reboots console after speech. In Classic 99, screen does not get scrambled, but speech is not recognisable and program crashes still after speech.

Think I also have an idea why, I expect it is clashing with the VGMComp2 code I use for music as that is also needing scratchpad space. Although no music function is invoked yet until after the speech is done..... and not sure if that also explains the screen getting scrambled completely.

So guess I need to do some more homework to get that working in my game in adjusting scratchpad (and maybe other) memory allocations.

 

Very much appreciate your effort by the way!

Edited by xahmol
  • Like 1
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...