M100 Small-C 85 Lib
Willard Goosey
goosey@sdc.org
4/16/2017

HISTORY:
Someone did a Small-C runtime for the Model 100 a long time
ago. Unfortunately that version has vanished from the Internet. Its
closest surviving kin is a NEC8201 Small-C, which may or may not be on
web8201.

Meanwhile, Small-C has survived as a niche and perhaps even
prospered, with many different versions available for may different
machines. Small C-85 is one of these, specifically supporting the
Model 100's 8085 and its "undocumented" instructions. 

Like most people, I dreaded the thought of having to handle even a
subset of C's standard library on the Model 100, which is an awesome
little machine but does not in any way resemble a UNIX box.

Then one day I unarchived CPC-NEC.ZIP and actually looked at how it
supported stdio. And to my great surprise, it _didn't_! A handful of
functions like getc() and putc() were mapped directly to ROM calls,
and that was it.

And I thought, heck, I can do that! So here we are. 


Requirements:

Small-C 85 : https://github.com/ncb85/SmallC-85
ASxxxx : http://shop-pdp.net
hex2co : From Chris Osburn's personal library at club100.org
	http://club100.org/memfiles/index.php?&direction=0&order=&directory=Chris%20Osburn/RBASIC%20Support
GNU Make: Standard GNU util 
objcopy: Standard GNU binutil


Building:

Download and install all of the above. I suggest editing the SmallC-85
makefile to specify the directory for global include files as
something convenient.

This archive will be available at http://www.sdc.org/~goosey/m100 and
in my personal library at Club100.

a "make alllibs" will make the library and install it in ./lib. At
this point you should be good to Small-C!


Use:

Please refer to the scc8080 and ASX documention for the compiler,
assembler, and linker themselves. Refer to the M100 Technical
Reference, m100.def, and the Covington maps for ROM routine details.

The current makefile rules set, by default, the starting address of
the M100 binary at 0xF000. If your binary tries to load above MAXRAM,
the Model 100 will COLD START! To set a different load address,
redefine the variable DEF_ADDRESS in the makefile:

	kque.ihx: DEF_ADDRESS=0xE800
	kque.ihx: kque.rel

Kque.ihx will be linked to start at 0xE800.

By default, the linker creates a .map file that describes the final 
addresses of all global symbols, as well as all the "modules" linked
in. Examine this file to determine the uppermost address of your
binary.


WARNING:
The stack starts below HIMEM and grows downwards. There are currently
no checks to make sure it doesn't clobber files.


To compile rstar.c into rstar.co make(1) will:

scc8080 -u rstar.c
#the compiler itself
#-u use undocumented opcodes

as8085 -g -o rstar.rel rstar.s
#the assembler. 
#-g don't complain about unknown globals
#-o output file

aslink -n -i -m -b M100_CODE=0xF000 -o rstar.ihx lib/cstart.rel rstar.rel -l small.lib 
#linker
#-n = don't echo commands to stdout
#-i = hex file out
#-m = generate map file
#-b modname=address module modname starts at address
#-l library library file
#-o = output file

objcopy -I ihex -O ihex --gap-fill 0 rstar.ihx rstar.hex
#reformat the hex file into something hex2co can handle
#-I input format
#-O output format
#--gap-fill 0 fill any gaps in the output file with 0's.

hex2co rstar.hex
#generate the .co





Library Reference:

This makes no real attempt to be portable to other machines, rather it
maps the Model 100's ROM API directly to C function calls.

This supports the BASIC command CALL <address>,A,HL. the formal
arguements for main() are main(unsigned char A, int HL). It can also
CALL further binaries that use the same convention with
bcall(address,a,hl).

This library inherits a very large number of symbols from m100.def. If
the linker complains about multiply-defined symbols, check your code
against m100.def for conflicts.  All symbols defined in m100.def are
uppercase.

Some ROM calls are directly callable: These take no arguements and
return nothing, so don't need wrapper functions. 


//Reboot functions
INITIO()  //cold start
IOINIT()  //warm start
BOOT()    //reboot


//ROM Apps. NO coming back from these!
MENU()	  //jump to main menu
TXT()    //ROM text editor
BASIC()  //BASIC
TELCOM()  // telcom app
ADDRSS()  //
SCHEDL()  // address/schedule database ROM app


//Keyboard Input

int getc()
int getchar()
	//blocking, echoing read 1 char from keyboard
Returns 0xFF<char> on "special" keys


int chget()
	//blocking nonechoing read 1 char from keyboard
Returns 0xFF<char> on "special" keys


char *gets()      
	//line input, returns system input buffer (INBUF)
INBUF will change whenever this is called!
INBUF is null terminated, 253 bytes long


char *getline(char *s)
	//line input, returns string in s
Assumes s is big enough. Clobbers INBUF as gets() above


int kyread()
	//non-blocking non-echoing keyboard read
returns FFxx if function key pressed (F1=0...F8=7,LABEL=8,
PRINT=9, shift-PRINT=0A, PASTE=0B


int keywaitu()
	//blocking nonechoing keyboard input, uppercases result


int chsns()
	//check keyboard queue for characters
return 1 if key pending
return 0 if no keys pending


int keyx()
	//check keyboard queue for chars or <break>
returns : 0 on keyboard queue empty
	1 on keys pending
	-1 on break pressed


int brkchk()
	//check for break or pause only
return 1 if <break> or <pause> pressed
return 0 otherwise


//LCD control

CRLF()	//print ^M^J
HOME()  //home cursor
CLS()	//clear screen
SETSYS()  //lock line 8
RSTSYS()  //unlock line 8
LOCK()	  //lock screen (no scrolling)
UNLOCK()  //unlock screen (enable scrolling)
CURSON()  //turn on text mode cursor
CUROFF()  //turn off text mode cursor
DELLIN()  //delete current line
INSLIN()  //insert line
ERAEOL()  //erase line from cursor to end of line
ENTREV()  //turn on  reverse video
EXTREV()  //turn off reverse video
DTLINE()  //print day/date/time as it appears on MENU
TDDPT()   //print time on top line of screen
PFRE()    //print number of bytes of free memory

putc(char c)
	//write one char to LCD


putd(int i)
	//write an integer to LCD


puts(char *s)
	//write a string to LCD


esca(char c)
	//print escape code <esc>-c to LCD
note esccode.h


//Thanks to Daryl Tester for these!
hex4(int i) 
	//print 1 hex digit

hex8(int i) 
	//print 2 hex digits

hex16(int i) 
	//print 4 hex digits


plot(int x, int y)
	//set dot x,y 240x64 screen


unplot(int x, int y) 
	//clear dot x,y 240x64 screen


barpos(int i)
	//locate bar cursor on location i (0-23)


barcur(int i)
	//toggle bar cursor to opposite state i=loc 0-23



//Time and Date functions

char *time(char *s)
	//return current time in s
Assumes s is large enough (XX:XX:XX\0 = 9+ chars)


char *date(char *s)
	//return current date in s
Assumes s is large enough (XX/XX/XX\0 = 9+ chars)


char *day(char *s)
	//return day name in s
Assumes s is large enough (XXX\0 = 4+ chars)



//function key support

The function key table is a C string that, within it,has 8
parity-bit-set terminated substrings.

In assembly this looks like:
;  fctab defm 'files' ;f1
;        defb #0x0D
;        defb #0x80
;        defm 'load'   ;f2
;        defb #0x80
;         defb #0x80      ; no function for f3
;......
;        defm 'menu',#0x8D ; f8

Unfortunately Small C-85 doesn't support backslash-escaped octal or hex
literals. "foo\n\x80..." doesn't work. :-( 

CLRFLK()  //clear function key table
DSPFNK()  //display function keys
ERAFNK()  //erase function key display
FNKSB()   //display function key table if enabled
BK2SK()   //install BASIC function key table

stfnk(char *s)
	//set function key table from s 


stdspf(char *s)
	//set and display function key table from *s


//Thanks to Alex Evans for this!
makfnkt(int p[], char *s)
	//p[] is actually an array of 8 char pointers
each one is a C string with a function key definition and can be
null. S is a single string assumed to be large enough to hold the
actual key table string, which will have parity-bit set terminated
words and also will be a null-teriminated C string.

Example Fragment:
  ar[0]="foo\r";  //4
  ar[1]="bar\r";  //4
  ar[2]="baz\r";  //4
  ar[3]=0;   //1
  ar[4]=0;  //1
  ar[5]="goo";  //3
  ar[6]="ber";  //3
  ar[7]="MENU\r"; //5
  makfnkt(ar,mytab);
  stdspf(mytab);



//Subroutine support

#include "varptr_string.h"
#include "fdb.h"
bcall(int address, char a, int hl)
	//BASIC-style CALL to address <address>, with arguements a and
hl in the appropriate registers. This is to allow useful subroutine
modules like Ken Pettit's awesome ASCPIX to be used from a C program.

Such binary modules may expect the results of a call to BASIC's
VARPTR() function. These results are:
	VARPTR(integer) = pointer to the int value
	VARPTR(string) = struct vs *pointer (see varptr_string.h)
	VARPTR(float) = pointer to 4-byte float value
	VARPTR(double) = pointer to 8-byte float value
	VARPTR(#file) = pointer to File Descriptor Block (see fdb.h)

Due to quirks in the SmallC-85 compiler, if there is more than one
globally-defined struct (such as those included in dir.h, fdb.h, or
varptr_string.h), there must be a globally-declared instance of each.

Note that it is complicated to keep multiple HIMEM programs in the
HIMEM area.



//printer

PRTLCD()  //dump lcd to printer


int printr(char c)
	//print char c on printer, returns -1 on error 
This call does not eat line feeds.


pnotab(char c)
	//print char c on printer, do not expand tab	
This call does not eat line feeds.


prttab(char c)
	//print char c on printer, expand tab to spaces
This call also eats line feeds.



//serial port/modem

DISC()    //disconnect phone line
CONN()    //connect phone line
SENDCQ()  //send XON (^Q) to serial port
SENDCS()  //send XOFF (^S) to serial port
CLSCOM()  //deactivate rs232/modem

dial(char *s)
	//dial phone number pointed to by char *s


int rcvx()
	//return number of chars in rs232 recieve queue


int rv232c()
	//return char from rs232 queue 
	-1 on error 
	-2 on BREAK


sd232c(char c) 
	//send c out serial port with soft flow control


sndcom(char c)
	//send c out serial port with no soft flow control


int cardet()    //(UNTESTED)
	//returns 1 if built-in modem has carrier


baudst(int i) 
	//set rs232 baud rate. Serial port must be activated first.
i= 1..9


inzcom(int flag, int baud, char config)
	//initialize serial port or modem 
flag=1 for rs232, 0 for modem
baud=baud rate 1..9
config bitmap:
0    number of stop bits 0=1, 1=2
1    parity setting      0=odd 1=even
2    parity disable      0=enable 1=disable
3-4   word length         01=6 10=7 11=8
this byte is ANDed with #0x1F to ignore bits 5-7 


setser(int flag, char *s)
	//set serial config and init rs232 or modem
flag=1 for rs232, 0 for modem
s=STAT block, 5 bytes not null terminated


//directory & File support

RAM files are complicated! Be careful!

When manipulating a file, it should be marked as OPEN by OR-ing the
file's directory flag byte with DIR_OPEN. To mark the file as closed,
AND the flag byte with DIR_CLOSE. (these are defined in dir.h) If the
file changed size in any way, be sure to call DIROK() to rebuild the
directory pointers.

DIROK()  //Fix directory pointers after a file has grown or shrunk,
which moves all the files above it around...


prsnam(char *s, int i)
	//parse a file.do filename into FILNAM system var
as "FILE  DO"
s=filename
i=length of filename


int maktxt(int i[])
	//create text file. prsnam() must be called first
Returns: i[0]=top (starting address) of new file
	 i[1]=address of directory entry
	 i[2]=1 if file existed, 0 if new file
	returns 1 if file existed, 0 if new file


#include "dir.h"
struct dir *chkdc(char *s) 
	//search for a 4.2 filename
returns address of directory entry or 0 if no file found


#include "dir.h"
struct dir *chkfn(char *s)
	//search for 6.2 file
returns address of directory entry or 0 if no file found


#include "dir.h"
struct dir *fndfil()
	//search for a file in directory
prsnam() must be called first
returns address of dir entry on file found or 0 on no file found


char *mkpnam(char *s0, char *s1)
	//convert s0 directory entry style name FILE  DO into
6.2 format FILE.DO stored in s1 (null terminated) 
s1 assumed to be at least 10 chars long
returns s1


#include "dir.h"
struct dir *nxtdir(struct dir *entry)
	//retrieve the next directory entry
entry=dir entry before starting point
returns pointer to dir entry on success or 0 on fail


#include "dir.h"
struct dir *FREDIR()
	//locates an empty directory slot
returns pointer to dir slot
drops to BASIC with FL ERROR if no free slots


#include "dir.h"
int flena(struct dir *entry)
	//Returns length of ASCII file in directory slot entry


#include "dir.h"
char *gtxttb(struct dir *file)    
	//gets beginning address of a file 
file=directory entry
returns starting address of file


#include "dir.h"
char *prgadd(struct dir *file)
	//gets beginning address of a file 
file=directory entry
returns starting address of file


#include "dir.h"
kilasc(char *top, struct dir *dir)
	//delete text file  
top=address of top of file
dir=address of directory entry
 

int inschr(char c, char *s) 
	//insert a character in a file 
c=char to insert
s=address to insert at
	//returns 0 on success
	//returns -1 on out of memory


int makhol(int i, char *s)  
	//insert a number of spaces
i=number of spaces(*)
s=address to insert at
returns 0 on success -1 on memory full

(*) "Spaces" here does not mean actual space (0x20) characters, but
something more nebulous. :-( If you makhol() make sure you poke actual
characters into that area or RAM corruption will occur. 


masdel(int i, char *s) 
	//delete a number of characters 
i=number of spaces
s=address to delete from


#include "dir.h"
char *makfil(struct dir *entry, char *start, char status)
	//create a BASIC, text, or binary file
entry=empty directory slot   (see FREDIR())
start=starting address
status=file status defined in dir.h
	DIR_BA = 0x80 = BASIC file (.BA)
	DIR_DO = 0xC0 = Text file (.DO)
	DIR_CO = 0xA0 = binary file (.CO) 

prsnam() must be called called first to set the file name. It is the
programmer's responsibility to make sure the filename doesn't already
exist in the the directory.  EOF must be manually inserted into the
file using makhol().

Returns starting address.

The starting address of a file depends on the file type. Refer to the
NEC 8201a Technical Reference for the full story, but in brief:

File Type			DO 		BA		CO
starting address		DOSTART		DOSTART-1	VARSTART
address told to MAKFIL		DOSTART-1 	DOSTART-2	VARSTART-1
EOF to insert			0x1A		0x00 0x00	CO header

When a file is created, an EOF record must be inserted at the file's
starting address. Text files begin at DOSTART and a 0x1A (Control-Z)
character must be insterted at DOSTART. However, to keep DIROK() from
becoming confused, when makfil() is called, it must be told that the
staring address is DOSTART-1. 

Likewise, a BASIC file is created at DOSTART-1, with makfil() informed
that it begins at DOSTART-2. BASIC EOF is 2x 0x00 characters. COSTART
will be incremented by the makhol() call but DOSTART has to be
manually incremented.

CO files, like BASIC files, are created at the end of their
region. VARSTART is a pointer to the beginning of BASIC variable
storage, which is just above CO files. A new CO file is created at
VARSTART, and makfil() is told it's address is VARSTART-1.  Instead of
a EOF character, CO files have a 6-byte header (START ADDRESS, LENGTH,
EXECUTE ADDRESS) and then LENGTH bytes of code. if makhol() is used to
allocate the space, COSTART must be preserved and restored after
makhol() is called.


char *indexfa(char *s, char *t)
	//Search for string t in a file, starting from s
s is an ASCII file, ^Z terminated
t is null-terminated C string
returns (relative) position of found string or -1 on EOF


//Cassette Functions

CTON()   //turn on cassette motor
CTOFF()  //turn off cassette motor
SYNCW()  //write cassette header and sync byte
SYNCR()  //read cassette header and sync byte

unsigned int findfreq()  //UNTESTED
//Find the frequency of the cassette port.  This routine measures the
duration from the start to the stop of half of the wave presented on
the cassette port.  The result is the number of 29 t-state cycles
required to find the end of the wave.  The byte at TRIGFLAG (FF8EH)
determines if the count will trigger on a high or low pulse.  For
example, a 1200 hz signal, which would take 416 ms to go through half
of the wave, would cause this routine to exit with a value aound 35.
If the sound option is turned on SOUNDFLAG (FF44H), this routine will
click the beeper on each call.  Note: Although this routine analyses
the cassette port in 29 T-state intervals, the actual routine requires
much more time to execute. 
	Returns number of 29 T-state intervals (255 max) or 0xFFFF if
BREAK was pressed.
 (Convington Map)


char datar() //UNTESTED
//read byte from cassette no checksum 


char casin(char *checksum)  //UNTESTED
//read byte from cassette with checksum. Byte read is returned,
checksum is updated.


char csout(char byte, char checksum) //UNTESTED 
//write byte to cassette, returns updated checksum


dataw(char byte) //UNTESTED
//write byte to cassette, no checksum


crecin(char *buffer, int size) //UNTESTED
//load a record of size bytes from cassette and store in buffer


 
//Sound Functions

BEEP()    //BASIC BEEP

#include "sound.h"
music(int freq, unsigned char dur)
	//generates a tone, frequency freq and duration dur
note sound.h


//Functions from SmallC-85

int abs(int num)
      // return absolute value 


int atoi(char s[])
	//convert string s to int


#include <stdio.h>
int binary(char *word, int table[], int n)
	//binary search for string word in table[0] .. table[n-1]
reference CPL pg. 125


int isalpha(c) char c;
	// return true if C alphabetic


int isupper(c) char c;
	//return true if c uppercase


int islower(c) char c;
	//return true if c lowercase


int isdigit(c) char c;
	//return true if c is a digit


int isspace(c) char c;
	//return true if c is whitespace


int toupper(c) char c;
	//return uppercase of c


int tolower(c) char c
	//return lowercase of c


#include <stdio.h>
int index(char s[], char t[])
	//index - find index of string t in s
reference CPL 67.


char *itoa(int n,char s[])
	//return character string of int n


srand(int x)
	//seed rng


int rand()
	//16-bit RND. This is not a very good RNG but it is fast.


char *reverse(char *s)
	// Reverse a character string, reference CPL p 59


shellsort(int v[], int n)
	//Shell sort of string v[0] .... v[n-1] into increasing
order.
Reference CPL pg. 108.
 

char *strcat(char *s1, char *s2)
	//Concatenate s2 on the end of s1.  
S1's space must be large enough.
Return s1.


int strcmp(char *s1, char *s2)
	//Compare strings:  s1>s2: >0  s1==s2: 0  s1<s2: <0


char *strcpy(char *s1, char *s2)
	//Copy string s2 to s1.  s1 must be large enough.
return s1


int strlen(char *s)
	//return length of string, reference CPL p 36 */


char *strncat(char *s1, char *s2, int n)
	//Concatenate s2 on the end of s1.  
S1's space must be large enough.
At most n characters are moved.
Return s1.


int strncmp(char *s1, char *s2, int n)
	//Compare strings (at most n bytes):  
s1>s2: >0  s1==s2: 0  s1<s2: <0


char *strncpy(char *s1, char *s2, int n)
	//Copy s2 to s1, truncating or null-padding to always copy n bytes
return s1


int inp(char pno)	//(UNTESTED)
	//Read I/O port pno


outp(char pno, char val)  //(UNTESTED)
	//Write value val to I/O port pno




System Variable Reference:

small.lib makes a large number of system variables inherited from
m100.def available. These are named and described in m100vars.h


Version history:
       
0.0.1	Initial release
0.0.2	Bug fixes, reorganization, added sound.h, better docs
0.0.3	getline(), dir.h support, bug fixes, function-key functions,
	doc reorganization, makfnkt(), inp(), outp()
0.0.4	fndfil() fixed! chkdc(), chkfn(), prsnam() tested
0.0.5	all the files needed, brief docs on test programs
0.0.6	index() fixed. indexfa(), flena() added. All file functions tested.
0.0.7	Printer routines tested.
0.0.8	findfreq() and friends
0.0.9	rv232c(), sndcom(), inzcom() fixed.
0.0.10	chget(),getc() returns ffxx on special keys
	casin(),csout(),datar(),dataw(), bcall(), makfil(),
	crecin() added.
