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 actually 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:

	prstar.ihx: DEF_ADDRESS=0xE800
	prstar.ihx: prstar.rel psuran.rel

Prstar.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 hallo.c into hallo.co make(1) will:

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

as8085 -g -o hallo.rel hallo.s
#the assembler. 
#-l generate list file
#-g don't complain about unknown globals
#-o output file

aslink -n -i -m -b M100_CODE=0xF000 -o hallo.ihx lib/cstart.rel hallo.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 hallo.ihx hallo.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 hallo.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).

Some ROM calls are directly callable: These take no arguements and
return nothing, so don't need wrapper functions. These are uppercase
because all symbols inherited from m100.def are uppercase.


//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

int chget()
	//blocking nonechoing read 1 char from keyboard

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 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
	0xFFFF 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 digit

hex16(int i) 
	//print 4 hex digit


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

char *date(char *s)
	//return current date in s
	//assumes s is large enough

char *day(char *s)
	//return day name in s
	//assumes s is large enough


//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 SmallC-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);

//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
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)  // (UNTESTED)
	//dial phone number pointed to by char *s

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

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

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

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

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

baudst(char c)  // (UNTESTED)
	//set rs232 or modem baud rate
	//c= '1'..'9','M'

inzcom(int i,char baud,char config)  // (UNTESTED)
	//initialize serial port or modem 
	//i=1 for rs232, 0 for modem
	//baud=baud rate '1'..'9','M'
	//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)     //(UNTESTED)
	//set serial config and init rs232 or modem
	//flag=1 for rs232, 0 for modem
	//s=STAT block, 5 bytes 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 *s)    
	//gets beginning address of a file 
s=directory entry
returns starting address  of file

#include "dir.h"
char *prgadd(struct dir *file)
	//return 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

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

findfreq()
//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.  (Convington Map)
Returns number of 29 T-state intervals or -1 if BREAK was pressed.

//Sound Functions

BEEP()    //BASIC BEEP

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



