Loading files from disk using firmware (C & ASM with SDCC)

Pincha aquí para verlo en español

In this tutorial we will see how to read files from disk in the easiest way possible, using the Firmware. When a program is executed with run"program, ther disk rom is disabled, so you have to make a trick to re-enable it. I have borrowed an example from Kevin Thacker as a reference to do so. Basically we use the functions of the firmware mc_start_program y kl_rom_walk to use disc functions. Then for loading a file, nothing easier than using the functions cas_in_open, cas_in_direct y cas_in_close.

For examples of loading files, I used ConvImgCPC to convert some photos of some friends at a Christian camp to scr format. Let's see the first program, which loads an image on the screen:

////////////////////////////////////////////////////////////////////////
// Load01.c
// Mochilote - www.cpcmania.com
////////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <string.h>


////////////////////////////////////////////////////////////////////////
//SetupDOS - Prepare DOS for file load
////////////////////////////////////////////////////////////////////////
void SetupDOS()
{
  //Based on http://cpctech.cpc-live.com/source/loader.html
  __asm
    ld l, 2 (ix) ;stack return address
    ld h, 3 (ix) ;stack return address
    ld (_stack+1), hl

    ;;------------------------------------------------------------------------
    ;; store the drive number the loader was run from
    ld hl,(#0xbe7d)
    ld a,(hl)                  
    ld (_drive+1),a

    ;;------------------------------------------------------------------------
    ld c,#0xff          ;; disable all roms
    ld hl, #_start222         ;; execution address for program
    call #0xbd16  ;;mc_start_program    ;; start it

    _start222:: nop

    call #0xbccb  ;;kl_rom_walk     ;; enable all roms 

    ;;------------------------------------------------------------------------
    ;; when AMSDOS is enabled, the drive reverts back to drive 0!
    ;; This will restore the drive number to the drive the loader was run from
    _drive: ld a, #0x00
    ld hl,(#0xbe7d)
    ld (hl),a     

    _stack: ld hl, #0x0000
    push hl //one for sdcc ix pop
    push hl //two for return address
  __endasm;
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//Load filename to address
////////////////////////////////////////////////////////////////////////
unsigned char nFileNameLen = 0;
void LoadFile(char *sFileName, char *pLoadAddress)
{
  nFileNameLen = strlen(sFileName);
  __asm
    ;; B = length of the filename in characters
    ld hl, #_nFileNameLen;
    ld b, (hl)

    ;; HL = address of the start of the filename
    LD L, 4 (IX) ;sFileName
    LD H, 5 (IX) ;sFileName

    ;; DE = address of a 2k buffer
    ;; in disc mode: this buffer is not used when CAS IN DIRECT
    ;; firmware function is used, so it is safe to put it anywhere
    ;; you want.
    ld de, #0x0

    ;; firmware function to open a file for reading
    call #0xbc77 ;;cas_in_open

    ;; firmware function to load the entire file
    ;; this will work with files that have a AMSDOS header (ASCII
    ;; files do not have a header)
    ;; HL = load address
    LD L, 6 (IX) ;pLoadAddress
    LD H, 7 (IX) ;pLoadAddress

    ;; read file
    call #0xbc83 ;;cas_in_direct

    ;; firmware function to close a file opened for reading
    call #0xbc7a ;;cas_in_close

  __endasm;
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//SetColor
////////////////////////////////////////////////////////////////////////
void SetColor(unsigned char nColorIndex, unsigned char nPaletteIndex)
{
  __asm
    ld a, 4 (ix)
    ld b, 5 (ix)
    ld c, b
    call #0xBC32 ;SCR SET INK
  __endasm;
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//SetPalette
////////////////////////////////////////////////////////////////////////
void SetPalette(const unsigned char *pPalette, unsigned char nNumColors)
{
  unsigned char nColor = 0;

  for(nColor = 0; nColor < nNumColors; nColor++)
    SetColor(nColor, pPalette[nColor]);
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//SetMode
////////////////////////////////////////////////////////////////////////
void SetMode(unsigned char nMode)
{
  __asm
    ld a, 4 (ix)
    call #0xBC0E ;SCR_SET_MODE
  __endasm;
}
////////////////////////////////////////////////////////////////////////



////////////////////////////////////////////////////////////////////////
//main
////////////////////////////////////////////////////////////////////////
main()
{
  SetupDOS();

  SetMode(0);

  LoadFile("01.scr", (char *)0xC000);
  SetPalette((char *)0xD7D1, 16);

  while(1);
  
  return 0;
}
////////////////////////////////////////////////////////////////////////

If you compile and load the binary we get the following:

As you can see we have SetupDOS function that prepares the system to use the disk rom and we have the function LoadFile that loads the file that you say in the direction we want. So easy and simple :-) If we look at the role SetupDOS see that in the beginning the return address of the function is saved, this is so because when call mc_start_program, the stack is reset, is important to call SetupDOS at the beginning of main function and not use local variables in main, because would be added to the stack and when returning from SetupDOS, would have garbage. If you need variables in the main function we can use global or simply call a new function. The following example demonstrates this. The program is a slideshow of pictures:

////////////////////////////////////////////////////////////////////////
// Load02.c
// Mochilote - www.cpcmania.com
////////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <string.h>


////////////////////////////////////////////////////////////////////////
//SetupDOS - Prepare DOS for file load
////////////////////////////////////////////////////////////////////////
void SetupDOS()
{
  //Based on http://cpctech.cpc-live.com/source/loader.html
  __asm
    ld l, 2 (ix) ;stack return address
    ld h, 3 (ix) ;stack return address
    ld (_stack+1), hl

    ;;------------------------------------------------------------------------
    ;; store the drive number the loader was run from
    ld hl,(#0xbe7d)
    ld a,(hl)                  
    ld (_drive+1),a

    ;;------------------------------------------------------------------------
    ld c,#0xff          ;; disable all roms
    ld hl, #_start222         ;; execution address for program
    call #0xbd16  ;;mc_start_program    ;; start it

    _start222:: nop

    call #0xbccb  ;;kl_rom_walk     ;; enable all roms 

    ;;------------------------------------------------------------------------
    ;; when AMSDOS is enabled, the drive reverts back to drive 0!
    ;; This will restore the drive number to the drive the loader was run from
    _drive: ld a, #0x00
    ld hl,(#0xbe7d)
    ld (hl),a     

    _stack: ld hl, #0x0000
    push hl //one for sdcc ix pop
    push hl //two for return address
  __endasm;
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//Load filename to address
////////////////////////////////////////////////////////////////////////
unsigned char nFileNameLen = 0;
void LoadFile(char *sFileName, char *pLoadAddress)
{
  nFileNameLen = strlen(sFileName);
  __asm
    ;; B = length of the filename in characters
    ld hl, #_nFileNameLen;
    ld b, (hl)

    ;; HL = address of the start of the filename
    LD L, 4 (IX) ;sFileName
    LD H, 5 (IX) ;sFileName

    ;; DE = address of a 2k buffer
    ;; in disc mode: this buffer is not used when CAS IN DIRECT
    ;; firmware function is used, so it is safe to put it anywhere
    ;; you want.
    ld de, #0x0

    ;; firmware function to open a file for reading
    call #0xbc77 ;;cas_in_open

    ;; firmware function to load the entire file
    ;; this will work with files that have a AMSDOS header (ASCII
    ;; files do not have a header)
    ;; HL = load address
    LD L, 6 (IX) ;sFileName
    LD H, 7 (IX) ;sFileName

    ;; read file
    call #0xbc83 ;;cas_in_direct

    ;; firmware function to close a file opened for reading
    call #0xbc7a ;;cas_in_close

  __endasm;
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//SetColor
////////////////////////////////////////////////////////////////////////
void SetColor(unsigned char nColorIndex, unsigned char nPaletteIndex)
{
  __asm
    ld a, 4 (ix)
    ld b, 5 (ix)
    ld c, b
    call #0xBC32 ;SCR SET INK
  __endasm;
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//SetPalette
////////////////////////////////////////////////////////////////////////
void SetPalette(const unsigned char *pPalette, unsigned char nNumColors)
{
  unsigned char nColor = 0;

  for(nColor = 0; nColor < nNumColors; nColor++)
    SetColor(nColor, pPalette[nColor]);
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//SetMode
////////////////////////////////////////////////////////////////////////
void SetMode(unsigned char nMode)
{
  __asm
    ld a, 4 (ix)
    call #0xBC0E ;SCR_SET_MODE
  __endasm;
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//LoadLoop
////////////////////////////////////////////////////////////////////////
void LoadLoop()
{
  unsigned char BlackPalette[] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
  int nImage = 0;
  char sFile[32];

  while(1)
  {
    for(nImage = 1; nImage < 10; nImage++)
    {
      sprintf(sFile, "0%d.scr", nImage);
      LoadFile(sFile, (char *)0x6000);
      SetPalette((char *)BlackPalette, 16);
      memcpy((char *)0xC000, (char *)0x6000, 0x3FFF);
      SetPalette((char *)0xD7D1, 16);
    }
  }
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//main
////////////////////////////////////////////////////////////////////////
main()
{
  SetupDOS();

  SetMode(0);

  LoadLoop();

  return 0;
}
////////////////////////////////////////////////////////////////////////

If you compile and load the binary we get the following:

In this simple way we can load files and for example program a loader in c :-). You could download a zip with all files (source code, bat to compile, binary and dsk's) here: Files.zip

 

www.CPCMania.com 2014