Upload support using cc65
The cc65 Lynx library has the uploader functionality defined in uploader.s. It has the logic and ISR previously discussed compiled into the object file uploader.o and included in lynx.lib. The compilation of the assembly file exports a symbol __UPLOADER__ with an absolute value of 1 to allow it to be included when required.
It is important to note that this implies the upload logic is not included in your program by default. You will need to add this manually and also load the appropriate segment manually before the interrupt logic is needed. In other words, you should load the file segment with the ISR and upload logic before the upload routine is started. If memory is of no concern, you can load the routine during startup at the cost of some pre-allocated RAM segment. Alternatively, you can load it just in time, right before the upload functionality is enabled.
The cc65 has a special configuration file lynx-uploader.cfg for the uploader available, referring to the symbol __UPLOADER__. It defines a module UPLOAD with corresponding segments UPCODE and UPDATA.
In the lynx-uploader.cfg you can find the extra symbols referring to upload:
SYMBOLS {
# Other symbols omitted for brevity
__BOOTLDR__: type = import;
__UPLOADER__: type = import;
__UPLOADERSIZE__: type = export, value = $61;
__HEADERSIZE__: type = export, value = 64;
}
The __UPLOADER__ symbol needs to be imported to give the linker access to the uploader part in lynx.lib that was added by the uploader.o object file. The uploader code has a well known size of $60 = 96 bytes. Together with the single byte of data needed it comes to a total of 97 ($61 ) bytes, which is reserved right as the UPLOAD memory block after the available game RAM in MAIN and before the video RAM at $C038:
MEMORY {
ZP: file = "", define = yes, start = $0000, size = $0100;
HEADER: file = %O, start = $0000, size = __HEADERSIZE__;
BOOT: file = %O, start = $0200, size = __STARTOFDIRECTORY__;
DIR: file = %O, start = $0000, size = 16;
MAIN: file = %O, define = yes, start = $0200, size = $C038 - __UPLOADERSIZE__ - $200 - __STACKSIZE__;
UPLOAD: file = %O, define = yes, start = $C038 - __UPLOADERSIZE__, size = $0061;
}
Additionally, you need to specify a file segment in your directory and game binary output that holds these segments. We will discuss this later during the implementation details.
The cl65 linker can build and link everything together using the following command in a Makefile:
target = tutorial-uploader.lnx
objects = main.o directory.o
$(target) : $(objects)
$(CL) -t $(SYS) -o $@ -v -C lynx-uploader.cfg -m uploader.map $(objects) lynx.lib
In the fragment above the objects are main.o for the main program and directory.o with the required directory entries for the main program and uploader segment. You can find the full source for this cc65 example uploader in the programming tutorial samples.
Adding upload capabilities to your code
If you want to include the upload capabilities to your code, you need to do several things during development:
- Add a file segment for uploader code in your file directory on the cartridge image
- Load uploader segment before receiving logic is triggered
- Change configuration for your game code to cater for uploader
- Implement logic to set timer 4 of Mikey to correct source period and reload for your desired baud rate
- Clear receive buffer before receive procedure starts
- Enable receive interrupt in serial control bits to start actual receiving
At runtime the Lynx program needs to go through related steps to enable and bootstrap the upload process. This is typically done from a menu option or static screen in the game or program. It is less common to enable it during gameplay. After activating the uploader in the Lynx you will need to have a program that sends the data over ComLynx from your client device to the Lynx. The uploader logic will take care of the rest.
Uploader file segment
The uploader logic will be loaded whenever needed by making a call to lynx_load with the file number inside your directory. Adding the file segment is similar to any other code and data segment in your cartridge layout.
Inside your directory.asm file include the symbols required for upload:
.export _UPLOAD_FILENR : absolute
.import __UPCODE_SIZE__
.import __UPCODE_LOAD__
.import __UPDATA_SIZE__
These are the export of the file number as created during the directory layout construction, so you can reference it later during lynx_load. The imports of the UPCODE size and load address as well as the UPDATA size is required to build a correct directory entry later:
; Uploader segment
_UPLOAD_FILENR=_MAIN_FILENR+1
entry mainoff, mainlen, uploadoff, uploadblock, uploadlen, __UPCODE_SIZE__ + __UPDATA_SIZE__, __UPCODE_LOAD__
In the example above, the upload file segment is included right after the main file. You can change it to be anywhere in the file structure by positioning it as such. The resultant file number _UPLOAD_FILENR will allow you to load it later when in C code by referencing it as an external integer:
extern int UPLOAD_FILENR;
void initialize()
{
lynx_load((int)&UPLOAD_FILENR);
// No ser_install needed
tgi_install(&tgi_static_stddrv);
joy_install(&joy_static_stddrv);
// Rest of initialization code omitted
}
The serial driver of cc65 does not need to be included if you only want to use the upload functionality. The ISR of uploader.s contain all logic and the other C code uses only the definitions of Mikey for serial control manipulation.
Changing to upload linker configuration
The cc65 has a separate linker configuration lynx-uploader.cfg that is ready to apply. You can use it in simple scenarios where you have a standard game with a single main game segment and only upload functionality added.
Your Makefile should specify the use of the uploader configuration file instead of the implied lynx.cfg by adding an extra argument -C to the linker cl65.
$(target) : $(objects)
$(CL) -t $(SYS) -o $@ -v -C lynx-uploader.cfg -m uploader.map $(objects) lynx.lib
$(CP) $@ $(BUILD)-$@
The lynx-uploader.cfg configuration reserves the uploader segment in RAM memory after MAIN and video memory. If you choose to load the uploader segment just-in-time you could alter this to overlap with a MAIN memory segment to contiguously connects to video memory. This will work if the loading of the uploader ISR logic will not overwrite an actively used part of MAIN memory. It is risky, but will save 97 bytes in cases where saving this memory is required for MAIN.