Search the Community
Showing results for 'gcc' in content posted in TI-99/4A Development.
-
Hey everybody, I've been lurking here for a while, and thought this group might be interested in a port I've made of GCC and the GNU Binutils tools (gas, ar, ln, etc.). I've done some testing with simple programs, and things seem stable enough for other people to play with the compiler if they're interested. Since my development machine runs Linux, it seems like the best way to distribute the required patches is via source. I can provide the binaries, but since it seems like everybody here runs Windows, that might not be too useful. But if I'm wrong, let me know. The compiler outputs are ELF format object files, so I've made tools to convert to cartridge and EA5 binary formats for the TI. The intermediary assembly files are TI compatible, so they could be fed into your assembler of choice if you prefer. GAS also uses TI compatible files, but provides some useful exensions (long label names being one of the more important ones). I'm still putting a development site together at insomnialabs.blogspot.com, which has links to the GCC and Binutils patches, but other tools I have are not yet available for download (I'm working on it..). I'm attaching a "hello world" program as a teaser using GCC, GAS, LN, and the elf-to-cart converter. So, example code (included in the attachment): #define VDP_READ_DATA_REG (*(volatile char*)0x8800) #define VDP_WRITE_DATA_REG (*(volatile char*)0x8C00) #define VDP_ADDRESS_REG (*(volatile char*)0x8C02) #define VDP_READ_FLAG 0x00 #define VDP_WRITE_FLAG 0x40 #define VDP_REG_FLAG 0x80 static void vdp_copy_from_sys(int index, char* src, int size) { volatile char* end = src + size; VDP_ADDRESS_REG = index | VDP_WRITE_FLAG; VDP_ADDRESS_REG = (char)(index >> ; while(src != end) VDP_WRITE_DATA_REG = *src++; } void main() { // 12345678901234 vdp_copy_from_sys(0, "HELLO WORLD!",12); vdp_copy_from_sys(32, "THIS IS LINE 2", 14); while(1); } The resulting code after -O2 optimization: pseg LC0 text "HELLO WORLD!" byte 0 even LC1 text "THIS IS LINE 2" byte 0 even def main main li r1, 64 * 256 movb r1, @-29694 clr r1 movb r1, @-29694 li r1, LC0 L2 movb *r1+, r2 movb r2, @-29696 ci r1, LC0+12 jne L2 li r1, 96 * 256 movb r1, @-29694 clr r1 movb r1, @-29694 li r1, LC1 L3 movb *r1+, r2 movb r2, @-29696 ci r1, LC1+14 jne L3 L8 jmp L8 I was originally doing the port as a convenience for myself, so things may not be as well documented as they should. Let me know what you think. -------------------------------- Updated Aug 18, 2014: The build information and other handy info was scattered throughout this thread, making it hard to get the latest stuff. I'll make sure to keep this post updated to make things easier to find. Manually building Binutils: (from top level of source tree) $ patch -1 < BINUTILS_PATCHFILE $ ./configure --target tms9900 --prefix INSTALL_DIRECTORY --disable-build-warnings $ make all $ make install Manually building GCC: (from top level of source tree) $ patch -1 < GCC_PATCHFILE $ mkdir build $ cd build $ ../configure --prefix INSTALL_DIRECTORY --target=tms9900 --enable-languages=c $ make all-gcc all-target-libgcc $ make install Building Binutils and GCC using install script: $ install.sh INSTALL_DIRECTORY Binutils Changelog ------------------------- 1.5 Released 2013-05-01 Added more informative syntax error messages Fixed values like ">6000" in strings being mangled Confirm support for named sections 1.6 Released 2014-10-10 Added support for numeric registers Correct handling of comments Added support for dwarf debugging information 1.7 Released 2014-12-04 Restored ability to have label and code on same line Minor code cleanup GCC Changelog ----------------------- 1.8 Released 2013-05-01 Fixed R11 restoration in epilogue being dropped by DCE Added support for named sections Removed support for directly zeroing byte memory, was buggy in some memories 1.9 Released 2014-10-10 Changed order of jumps for less-than-or-equal tests to improve performance Fixed several integer type conversion bugs Corrected handling of variable shift by zero bits Fixed signed division Added support for dwarf debugging information 1.10 Released 2014-12-04 Prevented use of R0 as an address base Moved jump tables into text segment to free up space for variables Fixed bug which put initialized data in bss section Fixed negation of byte quantities Minor code cleanup 1.11 Released 2015-06-14 Fixed compilation error due to missing FILE macro in tms9900.h Some instruction sizes were defined incorrectly, causing assembly errors Fixed conditional jump displacement limits, they were too small. Added compilation pass to add needed SWPB instructions. 1.12 Released 2015-08-16 Fixed bug when dividing by constant value Improved type testing for instruction arguments Added text to "--version" flag output to show patch version. 1.13 Released 2016-11-23 Added compilation pass to better use post-increment addressing Ensured word alignment for symbols Removed optimization of tests against zero, they emitted unnecessary opcodes Fixed 32-bit shift instructions Fixed shift instructions to handle shift by zero bits Fixed and instruction to use ANDI when appropriate Added optimizations for shift of 32-bit value by 16 bits Fixed multiply to prevent using MPY with an immediate operand 1.14 Released 2017-02-19 Added tail call optimization Confirmed C++ support 1.15 Released 2017-05-29 Added .size directive to compiled output Fixed several instruction lengths Fixed multiply bug Reduced patch size 1.16 Released 2017-08-24 Fixed 32-bit right constant shift, failed with some constants Fixed all 32-bit variable shifts, sometimes used r0 as temp register Fixed carry bit in 32-bit add Fixed invalid instruction in some 32-bit add forms 1.17 Released 2018-10-25 More strict checks for address in BL commands Optimization for 32-bit left shift by 8 bits Optimization for 32-bit logical right shift by 8 bits Fixed 32-bit right shift by more than 16 bits Fixed 8-bit multiplies 1.18 Released 2018-10-31 Fixed 16-bit signed right shift Fixed 32-bit unsigned right shift 1.19 Released 2019-02-26 Removed side-effects from zero compares hello.tar.gz gcc-4.4.0-tms9900-1.0-patch.tar.gz binutils-2.19.1-tms9900-1.0-patch.tar.gz elf2cart.tar.gz elf2ea5.tar.gz binutils-2.19.1-tms9900-1.7-patch.tar.gz gcc-4.4.0-tms9900-1.11-patch.tar.gz gcc-4.4.0-tms9900-1.12-patch.tar.gz gcc-4.4.0-tms9900-1.13-patch.tar.gz gcc-4.4.0-tms9900-1.14-patch.tar.gz hello_cpp.tar.gz gcc-4.4.0-tms9900-1.15-patch.tar.gz hello2.tar.gz gcc-4.4.0-tms9900-1.16-patch.tar.gz gcc-4.4.0-tms9900-1.17-patch.tar.gz gcc-4.4.0-tms9900-1.18-patch.tar.gz gcc-4.4.0-tms9900-1.19-patch.tar.gz gcc-installer.tar.gz
-
Is there an existing method to translate text (char *) to the speech synthesizer's word library dynamically (while running), similar to Extended Basic's CALL SAY? A way is to generate a hash for each word to produce a lookup table, but the resources for its storage are not insignificant.
-
I'm not sure how useful this is going to be to anyone out there, but thought I'd share nevertheless... For another project I'm working on (or rather, playing around with...), I kept running into the issue that there is no support for malloc(), free() and the likes when using tms9900-gcc. Of course, since we're all rockstar programmers, and we all know which memory is used in our programs at all times, we'd rather do it manually since that's going to be the most efficient. But sometimes it's just more convenient to have all of that automated, right? So I've written a reusable malloc implementation that tries to be very memory efficient (it only introduces 1 word of overhead to keep track of the heap) and I believe is quite feature complete. It does sacrifice speed for memory efficiency, but on our platform I think that is the probably the most logical choice anyway. I have not tested this much beyond my other project and the simple test program that comes with it, but it does seem to work reliably so far. Feel free to play around with it and let me know if there's anything you'd do differently. https://github.com/themole-ti/libmalloc99
-
Copied from here for original question: C code supplied by Jedimatt42:
-
Hello, Since PLATOTERM is licensed under the GNU Public License, I have written a piece of accessory software that opens, and displays a copy of the GNU Public License 3.0 from a file. The TIPI version loads this from the web, courtesy of the PI.HTTP level 3 interface, which is very nifty. I do think that the dsr_xxxx functions here should be folded into libti99, but this is something that @tursillion may want to think about... Thanks to jedimatt42 for these functions, which I pulled from TIPICFG's source code. I have uploaded it to github, if you want to use it: https://github.com/tschak909/platoterm99-gpl You can run the program if you have a TIPI: CALL TIPI("PI.HTTP://TI99.IRATA.ONLINE/COPYING") And for the purposes of discussion, I am posting the code here, as well: #include <files.h> #include <string.h> #include <system.h> #include <conio.h> #define VPAB 0x3000 #define FBUF 0x3200 int force_quit=0; unsigned char dsr_openDV(struct PAB* pab, char* fname, int vdpbuffer, unsigned char flags); unsigned char dsr_close(struct PAB* pab); unsigned char dsr_read(struct PAB* pab, int recordNumber); void initPab(struct PAB* pab) { pab->OpCode = DSR_OPEN; pab->Status = DSR_TYPE_DISPLAY | DSR_TYPE_VARIABLE | DSR_TYPE_SEQUENTIAL | DSR_TYPE_INPUT; pab->RecordLength = 80; pab->RecordNumber = 0; pab->ScreenOffset = 0; pab->NameLength = 0; pab->CharCount = 0; } // Configures a PAB for filename and DV80, and opens the file unsigned char dsr_openDV(struct PAB* pab, char* fname, int vdpbuffer, unsigned char flags) { initPab(pab); pab->OpCode = DSR_OPEN; pab->Status = DSR_TYPE_DISPLAY | DSR_TYPE_VARIABLE | DSR_TYPE_SEQUENTIAL | flags; pab->RecordLength = 80; pab->pName = fname; pab->VDPBuffer = vdpbuffer; return dsrlnk(pab, VPAB); } unsigned char dsr_close(struct PAB* pab) { pab->OpCode = DSR_CLOSE; return dsrlnk(pab, VPAB); } // the data read is in FBUF, the length read in pab->CharCount // typically passing 0 in for record number will let the controller // auto-increment it. unsigned char dsr_read(struct PAB* pab, int recordNumber) { pab->OpCode = DSR_READ; pab->RecordNumber = recordNumber; pab->CharCount = 0; unsigned char result = dsrlnk(pab, VPAB); vdpmemread(VPAB + 5, (&pab->CharCount), 1); return result; } void main(void) { struct PAB pab; set_text(); charsetlc(); clrscr(); bgcolor(COLOR_CYAN); textcolor(COLOR_BLACK); gotoxy(0,0); unsigned char ferr = dsr_openDV(&pab,"PI.HTTP://TI99.IRATA.ONLINE/COPYING.TXT",FBUF,DSR_TYPE_INPUT); if (ferr) { cprintf("Could not open License from web."); for (; {} } int i=0; unsigned char ch; while (ferr == DSR_ERR_NONE) { unsigned char cbuf[81]; ferr = dsr_read(&pab,0); if (ferr == DSR_ERR_NONE) { vdpmemread(FBUF,cbuf,pab.CharCount); cbuf[pab.CharCount]=0; cprintf("%s",cbuf); } if (i>4) { i=0; cprintf(" \r\n"); cprintf(" -- PRESS ANY KEY TO CONTINUE -- "); ch=cgetc(); clrscr(); } else { i++; // Get next record. } } cprintf(" \r\n"); cprintf(" END OF LICENSE. PRESS ANY KEY TO QUIT. "); ch=cgetc(); ferr = dsr_close(&pab); } Hope it is useful. -Thom
- 1 reply
-
- 5
-
-
- programming example
- gcc
-
(and 2 more)
Tagged with:
-
In trying to come up with a strategy to win back memory for Alex Kidd, I was thinking about stuffing some code in a cartridge, so I can win back some of that 32kb expansion memory. Given that I'm currently already at nearly 16k of executable code (including constants), and that I still need to add a good number of features, I need to find a way to create bank switching software with gcc. What follows is a write-up of my ideas, not everything has been tested, and I'm looking for a sanity check: will this work, am I missing something that could simplify things? 1. Multiple pieces of code at the same location The first thing we need to do when hacking support for banked memory (such as bank switched cartridges) in gcc, is to tell the compiler that specific pieces of code will run from the same physical address space. In the case of a program designed to run from cartridge, this would be 0x6000. By default, gcc will put all executable code into a section called .text, and you can tell the linker to position this code at any location in memory by using command line options (--section-start .text=0x6000), or by creating a bespoke linker script and adding a properly configured SECTIONS section: SECTIONS { . = 0x6000; .text : { *( .text ) } . = 0xa000; .data : { *( .data ) } .bss : { *( .bss ) } } (Note: the above example requires a system with 32k memory expansion installed, since it puts all variables in expanded memory. It also requires a crt0 implementation that copies the initialization values for variables in the .data segment from somewhere in ROM or from disk to 0xa000) Since all code is in the .text segment by default, the linker will just start filling up memory with code from 0x6000 onwards, blasting past 0x7fff if the code segment happens to be larger than 8k and in the process creating a useless image for our purposes. At the very least, we can define our memory layout in the linker script to get a warning when one of our blocks exceeds the maximum size. We can do this by adding a MEMORY section to the linker script (there's no command line equivalent of this), and changing the SECTIONS section accordingly: MEMORY { cart_rom (rx) : origin=0x6000, length=0x2000; /* cartridge ROM, read-only */ lower_exp (wx) : origin=0x2080, length=0x1F80; /* 8k - 128 bytes */ higher_exp (wx) : origin=0xa000, length=0x6000; scratchpad (wx) : origin=0x8320, length=0x00e0; /* 32b is for workspace */ } SECTIONS { . = >cart_rom; .text : { *( .text ) } . = >higher_exp; .data : { *( .data ) } .bss : { *( .bss ) } } Now, whenever the .text section exceeds 8k, the linker will throw an error and abort. At least we'll know our program is too big to fit in the 8k, but it would be even better if we could stuff more code in other parts of memory. Unfortunately, ld will not do this for us, and we'll need to explicitly assign code to different sections in our source files by adding attributes to the function definitions. Supposing we already have filled our 8k of cartridge ROM, we could for instance decide to put additional functions in the lower 8k of the 32k memory expansion. First we add the section attribute to each function we want to put in the lower memory expansion area: void somefunction(int somearg) __attribute__ ((section ( .moretext ))); void somefunction(int somearg) { // some code } We now have code that will get put in the .moretext section, so we need to tell the linker where to put this code (assuming the same MEMORY section as in the example above): SECTIONS { . = >cart_rom; .text : { *( .text ) } . = >lower_exp; .moretext : { *( .moretext ) } . = >higher_exp; .data : { *( .data ) } .bss : { *( .bss ) } } (Note: again we need to remember that the cart will need to load the contents of section .moretext from somewhere in ROM or from disk and copy it to the lower memory expansion at 0x2080) In theory, we could automate the annotation of functions by doing two compilation passes: one with all code in the standard .text segment to discover the size of each compiled symbol, and one that uses that info to assign individual functions to the two available sections. In practice, I imagine this is doable enough by hand for most programs. Also, on our platform gcc doesn't seem to support calculating the size of individual compiled symbols, so by hand it is. So now we are able to put code into two different physical locations in the TI's memory, but that still doesn't allow for bank switching. As we said at the very beginning, for that we need to tell the linker that two or more sections of code need to target the same memory area. Turns out that we can do this with the OVERLAY command: SECTIONS { OVERLAY >cart_rom : AT 0x0000 { .text { *( .text ) } .moretext { *( .moretext ) AT ALIGN(0x2000)} } OVERLAY >higher_exp : AT ALIGN(0x2000) { .data : { *( .data ) } } .bss : { *( .bss ) } } Running the linker with a script with the above SECTIONS section will give us a binary that contains three 8k banks: .text, .moretext and .data (we ignore .bss, because those are just zero-initialized variables and are taken care of by our crt0 implementation). The code in the first two banks will expect to run at 0x6000, and expects to find the initialized data from the .data section at 0xa000. Given all this, we should be able to generate binaries in the right format to support bank switching. 2. Actually switching banks in code That was the easy part, after all, it didn't require any coding . However, the trickiest part to bank switching is to write code that can cope with switching from one bank to another (and have that new code return). There are a couple of ways to do this (some more cumbersome than others), but they will all share a common requirement: you need to keep a "bank switching stack" (for lack of a better term). That is to say, when code in bank 1 calls a function in bank 2, we need to save the return bank "location" (i.e. what enables "bank 1") somewhere. If that function in bank 2 then in turn calls a function in bank 3, we need to do the same thing without overwriting the first return bank location. This is a recursive problem, so we need a stack. The idea location for the bank switching stack seems to be in scratchpad, since it will be relatively small and that part of memory is always available. By putting the pointers to this stack in a separate section, we can use the linker script to put it there (or wherever else is convenient). The management of the stack needs to be done right before calling a function in another bank, and right before returning to the calling bank at the end of a function. On a select number of platforms, GCC supports so-called 'far' and 'near' pointers and/or function attributes, which could be used to implement two different function prologues/epilogues depending on the type of function call that needs to be done. Unfortunately, the tms9900 platform implementation does not support these attributes. GCC also has support for instrumenting each function call and return via the -finstrument-functions command line option. You need to implement your prologue and epilogue code in the following two functions somewhere in your code: void __cyg_profile_func_enter (void *, void *) __attribute__((no_instrument_function)); void __cyg_profile_func_exit (void *, void *) __attribute__((no_instrument_function)); However, the call to and return from __cyg_profile_func_enter happens /before/ the call to the actual function, so it would take some serious wrestling with the C call stack to transparently implement bank switching in these functions. Our last option is to instrument individual functions and function calls. This is certainly the most cumbersome implementation of all, but it is the only one which does not need embedded support in the compiler implementation itself. Instrumentation of the function call is relatively easy, keeping in mind that all manipulation of the bank switching stack needs to be done from within the calling bank and the absolute last command needs to be the one that triggers the switch to the next bank. The following process could be a usable implementation: The caller (code runs in bank 1): Writes the address and bank location of the intended callee in two registers (e.g. r0 and r1) Invokes the trampoline The trampoline (code runs in scratchpad/expmem): Saves the current bank on the bank switching stack Loads the new bank Makes the call using the info in (e.g.) r0 and r1 The callee (code runs in bank 2): Does stuff Returns to the trampoline The trampoline (code runs in scratchpad/expmem): Loads the original bank (which is popped from the bank switching stack) Returns to the caller Or, in other words, every function call should be structured as follows: caller calls trampoline(), trampoline calls callee, callee returns to trampoline, trampoline returns to caller. Using this type of construct, the trampoline function needs to transparently pass on all arguments to the callee. The easiest way to accomplish this is the have a bespoke trampoline function for each "far" function we're looking to call (with a "far" function being any function that runs from a bank switchable piece of memory). Something like the following example: // Our "far" function, in bank 2 int far_somefunction(int someint) __attribute__ ((section ( .bank2 ))); int far_somefunction(int someint) { // do something return somevalue; } // Our trampoline function, in non bankable memory (e.g. scratchpad) int somefunction(int someint) __attribute__ ((section ( .nonbankable ))); int somefunction(int someint) { // Set to bank 2, and push caller's bank on the stack push_bank(2); // Call far function retval = far_somefunction(someint); // Set caller's bank pop_bank(); return retval; } Using this, we can safely call somefunction() (our trampoline function for far_somefunction()) from anywhere in our code, no matter which bank we're currently in and no matter where the calling code resides in memory. Furthermore, we can also still call far_somefunction() directly from within the same bank if we want to avoid the overhead of the bank switching and the trampoline function. The big downside of course is that we now have one trampoline function for every "far" function we want to call, all with nearly identical function bodies, eating at our available non-bankable memory. Not a big deal if you plan on banking code in big chunks, but problematic if you have lots of little functions that you need to call from everywhere in your program. We could opt to create one generic trampoline function, using variable argument lists and function pointers, if we're really strapped for memory. The downside is that it would create even more overhead for every "far" function call you're looking to make. Even with bespoke trampoline functions for each far function, it's a good idea to limit the number of bank switching calls you need to do, especially if you're writing an action game that needs to retain a high frame rate, given the fairly high overhead the bank switching introduces. If the compiler had support for naked functions (functions without prologue and epilogue), we could probably reduce the overhead to an absolute minimum, similar to what you'd get with pure assembly code, but unfortunately gcc doesn't support that attribute on our target. I think the above is a sound strategy?
- 101 replies
-
- 2
-
-
Now that I have working implementations of PLATOTerm for Commodore 64, Apple II, and the Atari, I am doing my planning for other platforms, and TI 99/4A is on that list. I've found tursi's ti99 lib for gcc, which would be very suitable as a low level library, and there are lots of helpers for talking to the VDP, etc... but I am wondering if there are e.g. any line drawing and dot drawing primitives that anyone might have? or am I going to need to roll a pixel plot and bresenham line routine? -Thom
-
Hey guys, Now that I've got PLATOTerm successfully brought up on the 99/4A and displaying and decoding protocol data correctly, I need to wire in RS232 support. Could somebody well versed help out in what I need to be able to make an RS232 solution that would work reasonably well for most users? -Thom
-
I wasn't sure whether I should post this under the existing GCC thread or start a new thread. I'm putzing around with GCC and I think I may have found another bug when doing multiplies. From what I can tell there have been fixes for other multiply bugs. This may or may not be related. My environment: Fedora 25 Linux GCC 4.4.0 with the tms9900 1.14 patches I recall having an issue when running Insomniac's installer - there were some directories that the install was expecting to be there but weren't... All I did was create those directories and ran the installer again. Everything seemed to work fine. I don't know if this is relevant to my issue, but just putting it out there. So on to the code. I have two versions of a function that does a simple calculation using some input parameters. The first takes all required params and uses them to perform the calculation. This version works just fine: unsigned int get_vdp_addr_inline(int width, int row, int col) { return row * width + col; } Code that calls the above: unsigned int addr_inline = get_vdp_addr_inline(32, 1, 0); And now onto the buggy version. In this version the width is stored in a struct. The pointer to the struct is passed into the function instead of the width: typedef struct { int width; int height; int foo; int bar; } test_rect; unsigned int get_vdp_addr_struct(test_rect* rect, int row, int col) { return row * rect->width + col; } Code that calls the above: test_rect rect; rect.width = 32; rect.height = 24; rect.foo = 0; rect.bar = 0; unsigned int addr_struct = get_vdp_addr_struct(&rect, 1, 0); Like I said this version doesn't give me the correct result. In debugging it with Tursi's classic 99, I see the following assembly generated for the second version of the function above. I added some comments about what it is doing. ; Some memory locations: ; *0000: 83 E0 00 24 83 C0 09 00 ...$.... ; *3FF2: 00 20 00 18 00 00 00 00 . ...... ; Initial conditions when the function is called: ; R1 (3FF2) is the pointer to "rect" ; R2 is "row" param ; R3 is "col" param ; R1 == 3FF2, R2 == 0001, R3 == 0000 61FA C042 mov R2,R1 ; R1 == 0001, R2 == 0001, R3 == 0000 ; The above move instruction just blasted the "rect" pointer... this will not work. 61FC 3851 mpy *R1,R1 ; R1 == 0000, R2 == 83E0, R3 == 0000 ; This multiply does not make sense. ; Seems to be using the wrong registers. 61FE C042 mov R2,R1 ; R1 == 83E0, R2 == 83E0, R3 == 0000 6200 A043 a R3,R1 ; R1 == 83E0, R2 == 83E0, R3 == 0000 6202 045B b *R11 ; return to caller, INCORRECT result in R1 In case it's relevant, these are the GCC / Linker flags I am using: C_FLAGS=\ -O2 -std=c99 -s --save-temp -fno-builtin -Wall -Wstack-protector LDFLAGS=\ --section-start .text=6000 --section-start .data=2000 To me, it looks like a bug in the compiler output; however, it is possible it's some kind of user error. Hopefully Insomniac will see this at some point. I think my workaround for now is to hand code some assembly in place of the buggy function.
-
This is a library which adds single-precision IEEE 754 floating point operations. It provides the implementation for internal GCC calls. This means that the "float" type can be used in C programs without any additional effort. I've tried to make this library as small as possible, but at the same time tried to avoid writing a cryptic mess. All code has been tested and should be error-free, but let me know if anyone finds a bug somewhere. libfloat.tar.gz
- 1 reply
-
- 7
-
-
This is a libc implementation which is mostly compliant with the c89 standard. I've been working on this for a while as part of a disk-based OS for the TI, but this can still be used as an independent library. I chose this standard since more recent ones mainly add support for wide characters, complex floating-point data types and other stuff which is probably not useful for a TI99/4a system. All of the functions commonly used in stdio.h have been implemented, but there are two unresolved symbols: console_write and console_read. These are intended to provide text-based screen output and keyboard input respectively. In my OS project, these are provided by device drivers, but Tursi's libti99 would be better for anyone intending to use the stdio functions. Hopefully this library is useful for someone. Feel free to do whatever you want with this. libc99.tar.gz
-
So, I'm climbing on the shoulders of giants again. What do I need to do to set up TI GCC to output a image file suitable for use in the Flash99 cart? It would be neat to make small programs that don't require the 32K expansion if possible, but I can accept that reality if necessary. Thanks for any help you can provide.
-
After getting some help from the forums on AtariAge and some guidance from JediMatt, I have hacked together a simple game that is similar to the old Combat game on Atari, at least as I remember it (I haven't played it for years). My goal was to figure out how to use Tursi's libti99 and to mess around with sprites, etc in C. I used to try to make arcade games in XB 30 years ago, but with the speed of a compiled C program, it's a whole new game (pun definitely intended). Anyway I have attached the game in .bin format, since that was the easiest way for me to build and test it. I'm still learning how to do things but having a great time doing so. This game is far from perfect, but kind of playable the "AI" is a rudimentary and the enemy tank does occasionally get stuck in a pattern, but will try to navigate towards you. sometimes. If you move around, it should break free since it does randomly try to come after you. I put together a pseudo random number generator since there was no rand() yet, and while useful, it could use more entropy. I use the old trick of seeding the algorithm by counting clock cycles while sitting at the "PRESS SPACEBAR TO PROCEED" screen. It has some known bugs such as when playing single player, the enemy tank will get stuck in a wall. I definitely want to learn how to do better collision detection. Anyway I thought some of you might find it amusing, and feedback is certainly welcomed! I am having a blast with writing C code for the TI! I wanted to thank JediMatt for providing me some started code and for his cool font that he made I wouldn't have been able to dive into this stuff were it not for being able to search this forum for pointers. If anyone is interested in the source, I'll be glad to post it too. I'm refactoring some things and doing some cleanup, improving the "AI", and trying to squash a couple more bugs. My goal is to see about making a network-playable version eventually. Cheers, Corey/ElectricLab combatti.bin
-
So... I've been noodling with GCC on the TI99. https://youtu.be/exMGm6_q9Uc
