SmartBox OS: Difference between revisions

From Smart Box
Jump to navigation Jump to search
(Created page with "== Changelog == <nowiki>bob 071 Fixed MotorForward bug (%10) bob 072 CallOS 14 now checks current adc.owner status jim 072.d1 * Rewrite for new hardware/processor * jim 072.d1 Moved vectors/workspace to &440 jim 072.d1 Moved rs.inp.buf to &500 jim 072.d1 Moved jobout.buf to &600 jim 072.d1 Moved jobin.buf to &680 jim 072.d1 Moved jobs.status to &700 jim 072.d1 Added irq.X (&A4) jim 072.d1 Added irq.Y (&A5) jim 072.d1 Removed short detection code jim 072.d1 Voided CallO...")
 
m (Moved changelog below the documentation)
Line 1: Line 1:
== Changelog ==
<nowiki> Documentation for SmartBox OS 2.066
 
The term JobCall is the concept of the routine itself, as opposed to
JobName (which is the actual name associated with it (eg.  NameCode)) and
JobCode (which is the actual number associated with it (eg.  3)).
 
The term controller means the SmartBox, or any other incarnation
which may appear in future time !
 
The term OS means the controllers' Operating System.
 
 
Preview


  <nowiki>bob 071 Fixed MotorForward bug (%10)
There are a number of reasons why using Machine Code on the
bob 072 CallOS 14 now checks current adc.owner status
controller is more desirable than using the various JobCalls via the serial
jim 072.d1 * Rewrite for new hardware/processor *
link. One reason is speed, if the task which needs to be performed, has to
jim 072.d1 Moved vectors/workspace to &440
be performed very quickly (more quickly than can be achieved via the serial
jim 072.d1 Moved rs.inp.buf to &500
link at least), then some code must be written to work in the controller
jim 072.d1 Moved jobout.buf to &600
itself to achieve this sort of speed. If the task required is to be a
jim 072.d1 Moved jobin.buf to &680
"background" task (ie. a task which may happen while other "things" carry
jim 072.d1 Moved jobs.status to &700
on as normal, like a change of state of a particular port) then it is far
jim 072.d1 Added irq.X (&A4)
easier to let the controller do all the work, maybe under interupts, than
jim 072.d1 Added irq.Y (&A5)
continuously poll the controller checking the state of various things.  
jim 072.d1 Removed short detection code
Another reason may be that you would like to add another JobCall to the OS,
jim 072.d1 Voided CallOS' 6,7,8,9,10,11,13,16,17,18,19
extending the calls available to external applications (and the user at the
jim 072.d1 Removed JobCalls ReadADCReg,WriteADCReg
same time), without the user having to write the code himself to achieve the
jim 072.d1 Removed JobCalls ReadACIAReg,WriteACIAReg
aim. For advanced users, there is also the ablity to alter the way the OS
jim 072.d1 Removed JobCalls ReadVIAReg,WriteVIAReg,SetVIAHigh,SetVIALow
does various things and to have the controller in their "power", for
jim 072.d1 Added CallOS 21 Write Outputs
dedicated tasks etc.
jim 072.d1 Added CallOS 22 Read Outputs added
 
jim 072.d1 Added CallOS 23 Read Inputs added
One advantage of writing IN the controller is that that "function"
jim 072.d1 Added CallOS 24 Write Motors
is available to any computer using the serial link, and is thus not fixed to
jim 072.d1 Added CallOS 25 Read Motors
one computer type or even a computer at all, where the controller is used
jim 072.d1 Added CallOS 26 Read Keypad
free running, doing a particular task which doesn't need the intervention of
jim 072.d1 Expanded CallOS 5 information
the user's own computer.
jim 072.d1 Added JobCall IdentSystem to return CallOS 5 info
 
jim 072.d1 JobCalls now use appropriate CallOS'
 
jim 072.d1 OS.READBYTE no longer preserves A on CS
Call types
jim 072.d1 irq.vec,irq2.vec,nmi.vec now redundant
 
jim 072.d1 Renamed JobCall DownloadData to DownloadData38
There are three different types of machine code routines which can
jim 072.d1 Renamed JobCall UploadData to UploadData38
be added to the controller. Each one is suited to a more advanced level
jim 072.d1 Renamed JobCall ExecuteCode to ExecuteCode38
than the other, which also relates to the amount of work needed to actually
jim 072.d1 Renamed JobCall ReadByte to ReadByte38
get the routine working !  The three levels are such:
jim 072.d1 Renamed JobCall StoreByte to StoreByte38
 
jim 072.d1 JobCall ForcedADCRead now checks adc.owner first
a) Simple stand alone routine. Here the routine is just the routine
jim 072.d3 Added CallOS 27 Write printer
itself with no supporting code (ie. code to integrate with the rest of the
jim 072.d3 Added CallOS 28 Read printer
OS). It has to be called directly, by use of the ExecuteCode JobCall, thus
jim 072.d3 Added CallOS 29 Write RTC Reg
it usually resides at a fixed address and obviously does not offer the user
jim 072.d3 Added CallOS 30 Read RTC Reg
friendly way of accessing itself like there is with normal OS calls.
jim 072.d3 Added CallOS 31 Write RTC string
 
jim 072.d3 Added CallOS 32 Read RTC string
b) Extended JobCall. There is one JobCall in the OS (ExtendJob)
jim 072.d3 Added CallOS 33 Write RTC bcd
which can call various routines residing in memory, each one is identified
jim 072.d3 Added CallOS 34 Read RTC bcd
by a 8 bit number. The disadvantages of this way is that the call hasn't
jim 072.d3 Added CallOS 35 Write LCD Reg
got it's own JobName/JobCode and needs the extra byte to identify itself.  
jim 072.d3 Added CallOS 36 Read LCD Reg
This is ideal for the quick routine which the user would like to add for
jim 072.d3 Added JobCall WritePrinter
himself without having to resort to the extra code needed for the full
jim 072.d3 Added JobCall ReadPrinter
JobCode implementation.
jim 072.d3 Added JobCall PrintChar
 
jim 072.d3 Added JobCall PrintStreamZ
c) Full JobCall. This is where the routine can define itself one
jim 072.d3 Added JobCall PrintStream
(or a number) of JobNames/JobCodes, it can integrate itself into an
jim 072.d3 Added JobCall PrintServer
extension of the OS in a complete way. The disadvantage is that extra
jim 072.d3 Added JobCall WriteRTCReg
coding is needed to deal with the various OS calls it has to service and
jim 072.d3 Added JobCall ReadRTCReg
that extra work is needed to produce a suitable file to place in the
jim 072.d3 Added JobCall WriteRTC
controller.
jim 072.d3 Added JobCall ReadRTC
 
jim 072.d3 Added JobCall WriteRTCbcd
Normally with most processors, including the 65c02, the machine code
jim 072.d3 Added JobCall ReadRTCbcd
produced is produced for a fixed memory address, and it cannot be made to
jim 072.d3 Added JobCall WriteLCDReg
run in a different place in memory unless extra work is carried out by the
jim 072.d3 Added JobCall ReadLCDReg
user, or at least a combination of the user and the OS. For the controller
jim 072.d3 Added CallOS 37 Write Power ctrl
this is a very severe disadvantage as most extensions are downloaded as
jim 072.d3 Added CallOS 38 Read Power ctrl
"modules" and to optimise memory a system has to be devised to enable
jim 072.d3 Implemented OS.PRINTER
routines to be placed anywhere in memory, thus allowing no memory to be
jim 072.d3 Added JobCall PatchMF (MotorForward)
wasted. In the OS there is a routine to do just this, though it does need
jim 072.d3 Changed irq.vec to int0.vec
some help. A special version of the users code has to be developed, or
jim 072.d3 Changed irq2.vec to int1.vec
rather it has to be "processed" to add extra information on the end. Extra
jim 072.d3 Added int2irq.vec (wrksp+&20)
code is needed to set up a few pointers and call the OS routine to relocate.  
jim 072.d3 Added int3irq.vec (wrksp+&22)
See later.
jim 072.d3 Added int4irq.vec (wrksp+&24)
 
jim 072.d3 Added c0irq.vec (wrksp+&26)
 
jim 072.d3 Added c1irq.vec (wrksp+&28)
Memory
jim 072.d3 Added t1irq.vec (wrksp+&2A)
 
jim 072.d3 Added t2irq.vec (wrksp+&2C)
The first 256 bytes of memory are somewhat special, most zero page
jim 072.d3 Added txirq.vec (wrksp+&2E)
locations are reserved for use by the OS:
jim 072.d3 Added tyirq.vec (wrksp+&30)
 
jim 072.d3 Added s1rirq.vec (wrksp+&32)
0 General workspace, used to pass parameters to some OS
jim 072.d3 Added s1rirq.vec (wrksp+&34)
routines, can also be used by other routines DURING
jim 072.d3 Added s2irq.vec (wrksp+&36)
14 their execution.
jim 072.d3 Added adcirq.vec (wrksp+&38)
15 Reserved
jim 072.d3 Renamed JobCall DownloadData38 to DownloadData740
6f
jim 072.d3 Renamed JobCall UploadData38 to UploadData740
70 Available to the user
jim 072.d3 Renamed JobCall ExecuteCode38 to ExecuteCode740
9f
jim 072.d3 Renamed JobCall ReadByte38 to ReadByte740
a0 Accumulator store for irqs (irq_A)
jim 072.d3 Renamed JobCall StoreByte38 to StoreByte740
a1 Two byte counter, decremented at 100hz, useful for temporary
jim 072.d4 Hardware update
a2 timing purposes (fcount)
jim 072.d4 Re-assigned CallOS 37 to Write Power/Charge control
a3 RAM size, high byte of RAM size of machine, ie. for 32k
jim 072.d4 Re-assigned CallOS 38 to Read Power/Charge control
this will be &80 (RAM_size)
jim 072.d4 Moved int2irq.vec to wrksp+&22
a4 Reserved
jim 072.d4 Moved int3irq.vec to wrksp+&24
af
jim 072.d4 Moved int4irq.vec to wrksp+&26
b0 Used by the OS
jim 072.d4 Moved c0irq.vec to wrksp+&28
ff
jim 072.d4 Moved c1irq.vec to wrksp+&2A
 
jim 072.d4 Moved t1irq.vec to wrksp+&2C
The rest of memory space is used for the processors stack, OS'
jim 072.d4 Moved t2irq.vec to wrksp+&2E
buffers, tables, vectors and, of course, the user available memory.
jim 072.d4 Moved txirq.vec to wrksp+&30
 
jim 072.d4 Moved tyirq.vec to wrksp+&32
A breakdown of the rest of memory:
jim 072.d4 Moved s1rirq.vec to wrksp+&34
 
jim 072.d4 Moved s1rirq.vec to wrksp+&36
&100 to &1FF is the 65c02's stack.
jim 072.d4 Moved s2irq.vec to wrksp+&38
 
jim 072.d4 Moved adcirq.vec to wrksp+&3A
&200-&2FF is used by the OS for various system functions and also
jim 072.d4 Added OS.LCDVDU (&FFB7) and lcdvdu.vec (wrksp+&20)
contains the vector table, more on that later.
jim 072.d5 Re-assigned CallOS 37 to Write/Read Power/Charge control
 
jim 072.d5 Re-assgined CallOS 38 Read Battery Voltage
&300-&3FF is the serial input buffer.
jim 072.d5 OS.CALLOS now re-entrant
 
jim 072.d5 Fixed OS.PRINTER
&400-&47F is the internal job code call output buffer. (jobout_buf)
jim 072.d5 Started to add internal logging software
 
jim 072.d5 Moved OS.LCDVDU to &FFB6
&480-&4FF is the internal job code call input buffer. (jobin_buf)
jim 072.d7 Added OS.PRINTERPOLL (&FFB3) and printerpoll.vec (wrksp+&3C)
 
bob 073 Matched jim & bob sensor lookup tables
&500-&5FF is the job status table.
bob 073 Added PatchMF
 
jim 072.dj Added OS.CALLOS 51 Read keypad press
&600 to the end of RAM memory is for the user. The user should not
jim 072.dj Added hard reset keypad press
assume what the top of AVAILABLE memory is and use the
jim 072.dk Added JobCall ReadOutputs
system variable HIMEM.
jim 072.do Changed soft reset of zero page locations
 
jim 072.do Changed MotorForward/Backward bitmaps
In the controller the maximum limit of RAM is &8000 (which is 32k),
jim 072.dp Fixed OS.CALLOS WriteRTCbcd
from &8000-&DFFF lies the memory mapped hardware, from &E000 to &FFF9 lies
jim 072.dq Fixed OS.CALLOS ReadRTCstring for correct 24hr operation
the OS and from &FFFA to &FFFF lies the 65c02 vectors.
jim 072.dr Changed reset prompts
 
bob 074 Added support for Little Bob
So the user has his own limits of free memory. The lowest limit is
jim 072.dy Added further RTC/CMOS support
called LOMEM and the highest limit is called HIMEM, everything between these
jim 072.dB Removed double hard reset
boundaries is termed "available" for use by anyone, the user or the OS.  
jim 072.dE Set _cpu.mode depending on external memory requirement
Anyone can also claim memory, thus making private to them and not allowing
jim 072.dF Added Insight code
anyone else to use it. You do this by altering the value of LOMEM, HIMEM
jim 073 Sub-release
should not be moved.  Of course anyone claiming memory should first check
jim 074 Modification to LCD code - uses bsy flag all the time now
that there is enough memory available for the amount they want to claim for.  
jim 074 Fixed OS.CALLOS 42 Write LCD Char Def
To take a common example, a downloadable routine which installs itself as a
jim 074 Fixed OS.CALLOS 43 Read LCD Char Def
extension routine, The download routine will first of all read LOMEM, read
jim 075 Added simple battery charge code
HIMEM, check there is enough memory for the routine, download it to LOMEM
jim 075 Fixed JobCall ReadSensorTable
and call it, the downloaded routine will then relocate itself and alter
jim 075 Fixed OS.CALLOS 42 Write LCD Char Def
LOMEM so it is protected.
jim 075 Fixed OS.CALLOS 43 Read LCD Char Def
jim 075 Added sleep code
jim 075 Added OS.CALLOS 52 Write Sleep Time
jim 075 Added OS.CALLOS 53 Read Sleep Time
jim 075 Modified battery charge code
jim 076 Modified JobCall ReadSensorTable
jim 076 Modified OS.CALLOS 26 ReadKeypad to do debounce
jim 077 Rewrote battery charging code
jim 077.2 Reduced stop charge threshold to 2
jim 077.3 Increased stop charge threshold to 3
jim 077.3 Added battery voltage averaging
bill 077 Stripped
bill 077 Moved ins to out port
bill 077 Added RTS
bill 077 Changed sensor ID system back to original, using p2.1
bill 077 Dedicated USB/RS selection line, RTS provisioned on p2.3
bill 077 Overload "bounce" timing
bill 078 CTS added
bill 079 Temperature fudge</nowiki>


== Documentation ==
There is one other memory limit, this is called TOPMEM, this is the
absolute top limit of RAM, HIMEM will normally equal TOPMEM, but a future OS
may alter HIMEM to below TOPMEM.  TOPMEM should be ignored ideally, and
HIMEM used.


<nowiki> Documentation for SmartBox OS 2.066


The term JobCall is the concept of the routine itself, as opposed to
OS Calls
JobName (which is the actual name associated with it (eg.  NameCode)) and
JobCode (which is the actual number associated with it (eg.  3)).


The term controller means the SmartBox, or any other incarnation
The OS has calls (ie.  a jump instruction which you call, which
which may appear in future time !
then calls, via a RAM vector, a routine in the OS) setup near the top of
memory, through these calls you do everything associated with the
controller.  The calls are are such:


The term OS means the controllers' Operating System.
OS_PRINTER at address &FFB9
OS_CALLOS at address &FFBC
OS_SENDBYTE at address &FFBF
OS_READBYTE at address &FFC2
OS_SENDJOB at address &FFC5
OS_READJOB at address &FFC8
OS_DECODEJOB at address &FFCB


The address is the one to call for the particular routine.


Preview
OS_PRINTER is reserved for future use.
OS_CALLOS calls upon the OS to do various things internally.
OS_SENDBYTE sends a byte out of the serial port.
OS_READBYTE reads a byte from the serial port.
OS_SENDJOB 'sends' a value to the JobCall caller.
OS_READJOB 'reads' a value from the JobCalls caller.
OS_DECODEJOB decodes the Job number held in A and acts on it.
 
Note none of these things should be used in interupt routines,
except OS_CALLOS, of any sort, except if YOU know what state the machine is
in and WHY.


There are a number of reasons why using Machine Code on the
controller is more desirable than using the various JobCalls via the serial
link.  One reason is speed, if the task which needs to be performed, has to
be performed very quickly (more quickly than can be achieved via the serial
link at least), then some code must be written to work in the controller
itself to achieve this sort of speed.  If the task required is to be a
"background" task (ie.  a task which may happen while other "things" carry
on as normal, like a change of state of a particular port) then it is far
easier to let the controller do all the work, maybe under interupts, than
continuously poll the controller checking the state of various things.
Another reason may be that you would like to add another JobCall to the OS,
extending the calls available to external applications (and the user at the
same time), without the user having to write the code himself to achieve the
aim.  For advanced users, there is also the ablity to alter the way the OS
does various things and to have the controller in their "power", for
dedicated tasks etc.


One advantage of writing IN the controller is that that "function"
Serial Port
is available to any computer using the serial link, and is thus not fixed to
one computer type or even a computer at all, where the controller is used
free running, doing a particular task which doesn't need the intervention of
the user's own computer.


The serial port on the controller is currently based on the 6850
UART, this need not bother you as the OS deals with the thing entirely by
itself.  The chip is setup by the OS to always have a word format of 8n1 and
a baud rate of 9600, this translates to 960cps.  The 6850 has two divide
rates for the baud rate, the OS only uses one, using the other one is beyond
the scope of this document and shouldn't be neccessary to use.


Call types
Only receive interupts are used, transmission of characters is done
on a polled basis.


There are three different types of machine code routines which can
As said, there are two calls OS_READBYTE and OS_SENDBYTE.
be added to the controller.  Each one is suited to a more advanced level
OS_READBYTE tests the serial input buffer and returns the character (if any
than the other, which also relates to the amount of work needed to actually
in the A register), the c flag states whether there was a character
get the routine working !  The three levels are such:
returned.


a) Simple stand alone routine. Here the routine is just the routine
  Call : OS_READBYTE
itself with no supporting code (ie. code to integrate with the rest of the
  Entry : A, X, Y, c, z = undefined
OS). It has to be called directly, by use of the ExecuteCode JobCall, thus
  Exit : X, Y preserved
it usually resides at a fixed address and obviously does not offer the user
z = undefined
friendly way of accessing itself like there is with normal OS calls.
if c = 1 (no character received)
A is preserved
if c = 0 (character received)
A = character received


b) Extended JobCall.  There is one JobCall in the OS (ExtendJob)
OS_SENDBYTE sends the character in the A register out of the serial
which can call various routines residing in memory, each one is identified
port, taking into consideration flow control etc.
by a 8 bit number.  The disadvantages of this way is that the call hasn't
got it's own JobName/JobCode and needs the extra byte to identify itself.
This is ideal for the quick routine which the user would like to add for
himself without having to resort to the extra code needed for the full
JobCode implementation.


c) Full JobCall. This is where the routine can define itself one
  Call : OS_SENDBYTE
(or a number) of JobNames/JobCodes, it can integrate itself into an
Entry : A = character to send
extension of the OS in a complete way. The disadvantage is that extra
X, Y, c, z = undefined
coding is needed to deal with the various OS calls it has to service and
  Exit : A, X, Y, c is preserved
that extra work is needed to produce a suitable file to place in the
z = undefined
controller.


Normally with most processors, including the 65c02, the machine code
produced is produced for a fixed memory address, and it cannot be made to
run in a different place in memory unless extra work is carried out by the
user, or at least a combination of the user and the OS.  For the controller
this is a very severe disadvantage as most extensions are downloaded as
"modules" and to optimise memory a system has to be devised to enable
routines to be placed anywhere in memory, thus allowing no memory to be
wasted.  In the OS there is a routine to do just this, though it does need
some help.  A special version of the users code has to be developed, or
rather it has to be "processed" to add extra information on the end.  Extra
code is needed to set up a few pointers and call the OS routine to relocate.
See later.


OS_READJOB and OS_SENDJOB


Memory
To send/receive data from the user (either internally or via the
serial port) you call OS_SENDJOB/OS_READJOB for the next byte.  If your
JobCall requires dynamic use of data, ie.  interaction, then a check of the
enviroment should be done to check the call came from the serial port.  One
example of this is the OS JobCall MultipleServer, which requires dynamic use
of sending a stream of data until a reaction from the user to tell it to
stop.


The first 256 bytes of memory are somewhat special, most zero page
Call : OS_READJOB
locations are reserved for use by the OS:
Purpose: To read job value from the user
Entry : A, X, Y, c, z = undefined
Exit : A = byte read
X, Y is preserved
c, z = undefined
Note  : When coming from the serial port it waits for a character to be
received, if you are doing a serial only call and wish to have a loop
checking for a character from the user while doing "your own thing", then
OS_READBYTE should be used.  For internal calls this reads data out of
jobin_buf.


0 General workspace, used to pass parameters to some OS
Call : OS_SENDJOB
routines, can also be used by other routines DURING
Purpose: To give a job value back to the user
14 their execution.
Entry : A = byte to give
15 Reserved
X, Y, c, z = undefined
6f
Exit : A, X, Y is preserved
70 Available to the user
c, z = undefined
9f
  Note  : This will send either out to the serial port or place a byte in
a0 Accumulator store for irqs (irq_A)
jobout_buf. For internal calls this stores the value in jobout_buf, which
a1 Two byte counter, decremented at 100hz, useful for temporary
is allocated 128 bytes, no bound checking is done so sending more than 128
a2 timing purposes (fcount)
bytes to OS_SENDJOB will cause corruption of some memory.
a3 RAM size, high byte of RAM size of machine, ie. for 32k
this will be &80 (RAM_size)
a4 Reserved
af
b0 Used by the OS
ff
 
The rest of memory space is used for the processors stack, OS'
buffers, tables, vectors and, of course, the user available memory.


A breakdown of the rest of memory:


&100 to &1FF is the 65c02's stack.
OS_DECODEJOB


&200-&2FF is used by the OS for various system functions and also
JobCalls in practise are really only used by the user, JobCalls
contains the vector table, more on that later.
tend not to call other calls at all, infact you shouldn't really call
 
another call unless you were called from the serial port.  If you do need to
&300-&3FF is the serial input buffer.
call another JobCall then you do it like this; with each call you have
associated with it a number, and the call might require some data or give
some data back or both.  With the serial port this is simple, you just send
data down and receive data as it is produced.  You can't do this with
internal calls, and some calls will not let you call them from inside the
controller, you HAVE to call them from outside.  But for the ones which do
let you, you do it with two data blocks, one for input TO the call and one
for output FROM the call.  These are setup in a position in memory
(jobin_buf and jobout_buf), each one has a maximum of 128 bytes and this
should not be overflowed (OS_READJOB and OS_SENDJOB do NOT do any bound
checking).  So to start with you place in the input block the data you want
to give the call (if any), call it, and then act on the data in the output
block (if any). The OS call OS_DECODEJOB is the one which actually calls
the JobCall handler and eventually the actual routine, you tell the JobCall
handler which call you want by placing the JobCode in the A register.


&400-&47F is the internal job code call output buffer. (jobout_buf)
So for OS_DECODEJOB:


&480-&4FF is the internal job code call input buffer. (jobin_buf)
Call : OS_DECODEJOB
Entry : A = JobCode
X, Y, c, z = undefined
jobin_buf should contain any input data
Exit : A = flag
X = number of bytes given back by routine
Y = undefined
c = status, if set JobCall does not exist
z = status, if set JobCall cannot be called internally
jobout_buf contains any data sent from routine


&500-&5FF is the job status table.
A typical example would be:


&600 to the end of RAM memory is for the user. The user should not
LDA #readhimem ; JobCode for ReadHimem
assume what the top of AVAILABLE memory is and use the
JSR OS_DECODEJOB ; Call the JobCall handler
system variable HIMEM.
LDX jobout_buf ; Read HIMEM from buffer LSB
LDY jobout_buf+1 ; Read HIMEM from buffer MSB
STX jobin_buf ; Place LSB in input buffer
STY jobin_buf+1 ; Place MSB in input buffer
LDA #writelomem ; JobCode for WriteLomem
JSR OS_DECODEJOB ; Call the JobCall handler


In the controller the maximum limit of RAM is &8000 (which is 32k),
which would read HIMEM, and then write LOMEM to be the same value, leaving
from &8000-&DFFF lies the memory mapped hardware, from &E000 to &FFF9 lies
no spare memory left.
the OS and from &FFFA to &FFFF lies the 65c02 vectors.


So the user has his own limits of free memory.  The lowest limit is
The reason why you should not call an internal call from another
called LOMEM and the highest limit is called HIMEM, everything between these
internal call is that the same buffers (jobin_buf and jobout_buf) would be
boundaries is termed "available" for use by anyone, the user or the OS.
used for both calls, causing conflict between the two calls, or rather to
Anyone can also claim memory, thus making private to them and not allowing
the originator of the primary call !  The only time it would be safe if is
anyone else to use it.  You do this by altering the value of LOMEM, HIMEM
if you have already got all the data you want from jobin_buf and haven't
should not be moved.  Of course anyone claiming memory should first check
placed any in the jobout_buf (it would be lost if you had done).
that there is enough memory available for the amount they want to claim for.
To take a common example, a downloadable routine which installs itself as a
extension routine, The download routine will first of all read LOMEM, read
HIMEM, check there is enough memory for the routine, download it to LOMEM
and call it, the downloaded routine will then relocate itself and alter
LOMEM so it is protected.


There is one other memory limit, this is called TOPMEM, this is the
absolute top limit of RAM, HIMEM will normally equal TOPMEM, but a future OS
may alter HIMEM to below TOPMEM.  TOPMEM should be ignored ideally, and
HIMEM used.


CallOS


OS Calls
This OS call does various calls which are completely linked to the
OS, everything from relocating code to reading and writing the hardware
ports.


The OS has calls (ie.  a jump instruction which you call, which
As normal, a "function" request value is placed in the A register,
then calls, via a RAM vector, a routine in the OS) setup near the top of
any extra data is put in the X and Y registers and a call to OS_CALLOS is
memory, through these calls you do everything associated with the
made, and data is passed back in X, Y, c and z.  The A register will return
controller. The calls are are such:
0 for all routines except for calls 0 (deemed always to exist anyway), 12 (A
is reserved for future use), 14 (returns 1 instead) and 15 (12, 14 and 15 do
not return 12, 14, 15 in A though), if the routine passes back the same
value in A (the exception being call 0) as you gave it then that OS_CALLOS
function is not supported.


  OS_PRINTER at address &FFB9
  CALLOS : 0
  OS_CALLOS at address &FFBC
Purpose: Returns the OS version number
  OS_SENDBYTE at address &FFBF
  Entry : A = 0
  OS_READBYTE at address &FFC2
X, Y, c, z = undefined
OS_SENDJOB at address &FFC5
  Exit : A = Hardware release version
OS_READJOB at address &FFC8
XY = OS version number
OS_DECODEJOB at address &FFCB
c, z is preserved
  Note : Hardware version 0 is obsolete
  Hardware version 1 is SmartBox
  VIA at &8030
  UART at &8010
  ADC at &8000
  AUX_PORT (inputs) at &8020


The address is the one to call for the particular routine.
CALLOS : 1
Purpose: To claim a block of JobCodes
Entry : A = 1
X = number of JobCodes required
Y, c, z = undefined
Exit : A = 0
X = base JobCode, if 0, no big enough block available
Y = Job ID number, 0 if no big enough block
c = set if no big enough block
z is preserved
Note : This call claims JobCodes codes from the JobCode list.  JobCodes
are in the range 0 to 255, 0 is special and is the JobCall "Blank".  All the
OS ones are spaced out but an "application" can claim a block of JobCodes,
usually just the 1 call.  If there is not a contigous block big enough then
no JobCodes are allocated and the appropiate result is returned.  The Job ID
is from 0 to 255, 0 is unallocated, 1 is the OS calls, the rest are external
applications, each successful call increments the Job ID returned, meaning a
maximum of 255 successful calls (not important as their is not that many
free calls available !), the Job ID can be used to check whether a JobCall
request is yours.


OS_PRINTER is reserved for future use.
CALLOS : 2
OS_CALLOS calls upon the OS to do various things internally.
Purpose: To handle NameCode request for the application.
OS_SENDBYTE sends a byte out of the serial port.
Entry : A = 2
OS_READBYTE reads a byte from the serial port.
XY = block pointing to block of JobNames
OS_SENDJOB 'sends' a value to the JobCall caller.
c, z = undefined
OS_READJOB 'reads' a value from the JobCalls caller.
jobin_buf contains JobName (terminated by CR)
OS_DECODEJOB decodes the Job number held in A and acts on it.
zero_gp3 = JobId (as returned by CALLOS 1)
 
Exit : A = 0
Note none of these things should be used in interupt routines,
XY = undefined
except OS_CALLOS, of any sort, except if YOU know what state the machine is
z is preserved
in and WHY.
if c = 1 (Name not found)
if c = 0 (Name found)
jobin_buf contains the JobCode
Note : Used via the internal_vec to convert from JobName to job code, the
table format is just a list of the JobNames (each one CR terminated)
terminated by a &ff


CALLOS : 3
Purpose: To handle CodeName request for the application.
Entry : A = 3
XY = block pointing to block of JobNames
c, z = undefined
jobin_buf contains JobCode
zero_gp3 = job id (as returned by CALLOS 1)
Exit : A = 0
XY = undefined
z is preserved
if c = 1 (JobCode not found)
if c = 0 (JobCode found)
jobin contains JobName (terminated by CR)
Note : See above


Serial Port
CALLOS : 4
Purpose: To relocate code
Entry : A = 4
XY = execution address (offset from start of code)
c, z = undefined
zero_gp1 = offset address of execution after relocation
zero_gp2 = offset address of relocation BitMap
zero_gp3 = length of code to relocate
Exit : No exit, calls begining of code straight away
Note : See later section on Relocation


The serial port on the controller is currently based on the 6850
CALLOS : 5
UART, this need not bother you as the OS deals with the thing entirely by
Purpose: To "describe" the enviroment
itself. The chip is setup by the OS to always have a word format of 8n1 and
Entry : A = 5
a baud rate of 9600, this translates to 960cps.  The 6850 has two divide
X, Y, c, z = undefined
rates for the baud rate, the OS only uses one, using the other one is beyond
Exit : A = 0
the scope of this document and shouldn't be neccessary to use.
c, z is preserved
XY = address of table, format as such:
1st byte: number of bytes (ie. 14 currently)
byte 2+:
address of VIA
address of ACIA
address of ADC
address of AUX_PORT
address of jobs_status
address of jobin_buf
address of jobout_buf
Note : It is preferred that this call is consulted first if direct use of
the hardware is going to be used to get the correct addresses, or use CALLOS
0 and decide what addresses/configuration to use from the Hardware version
number.


Only receive interupts are used, transmission of characters is done
CALLOS : 6
on a polled basis.
Purpose: To read a register in the VIA
 
Entry : A = 6
As said, there are two calls OS_READBYTE and OS_SENDBYTE.  
X = register (0 to 15)
OS_READBYTE tests the serial input buffer and returns the character (if any
Y, c, z = undefined
in the A register), the c flag states whether there was a character
Exit : A = 0
returned.
Y = value read
X, c, z is preserved
Note : The register number is not checked for range. This call allows
access to the hardware without actually directly accessing the hardware
yourself.


  Call : OS_READBYTE
  CALLOS : 7
  Entry : A, X, Y, c, z = undefined
Purpose: To write to a register in the VIA
  Exit : X, Y preserved
  Entry : A = 7
z = undefined
X = register (0 to 15)
if c = 1 (no character received)
Y = value to write
A is preserved
c, z = undefined
if c = 0 (character received)
  Exit : A = 0
A = character received
X, Y, c, z is preserved
Note : See above


OS_SENDBYTE sends the character in the A register out of the serial
CALLOS : 8
port, taking into consideration flow control etc.
  Purpose: To read a register in the ACIA
 
  Entry : A = 8
  Call : OS_SENDBYTE
X = register (0 to 15)
  Entry : A = character to send
Y, c, z = undefined
X, Y, c, z = undefined
  Exit : A = 0
  Exit : A, X, Y, c is preserved
Y = value read
z = undefined
X, c, z is preserved
Note : See above


CALLOS : 9
Purpose: To write to a register in the ACIA
Entry : A = 9
X = register (0 to 15)
Y = value to write
c, z = undefined
Exit : A = 0
X, Y, c, z is preserved
Note : See above


OS_READJOB and OS_SENDJOB
CALLOS : 10
Purpose: To read a register in the ADC
Entry : A = 10
X = register (0 to 15)
Y, c, z = undefined
Exit : A = 0
Y = value read
X, c, z is preserved
Note : See above


To send/receive data from the user (either internally or via the
CALLOS : 11
serial port) you call OS_SENDJOB/OS_READJOB for the next byte.  If your
Purpose: To write to a register in the ADC
JobCall requires dynamic use of data, ie.  interaction, then a check of the
Entry : A = 11
enviroment should be done to check the call came from the serial port. One
X = register (0 to 15)
example of this is the OS JobCall MultipleServer, which requires dynamic use
Y = value to write
of sending a stream of data until a reaction from the user to tell it to
c, z = undefined
stop.
  Exit : A = 0
X, Y, c, z is preserved
Note : See above


  Call : OS_READJOB
  CALLOS : 12
  Purpose: To read job value from the user
  Purpose: To read a ADC channel
  Entry : A, X, Y, c, z = undefined
  Entry : A = 12
  Exit : A = byte read
X = channel number to read (0 to 3)
X, Y is preserved
Y, c, z = undefined
c, z = undefined
  Exit : A = reserved for future use
  Note   : When coming from the serial port it waits for a character to be
if c = 0 (8 bit reading)
  received, if you are doing a serial only call and wish to have a loop
Y = reading
  checking for a character from the user while doing "your own thing", then
X = undefined
  OS_READBYTE should be used.  For internal calls this reads data out of
if c = 1 (16 bit reading)
  jobin_buf.
X = reading (LSB)
Y = reading (MSB)
z = undefined
  Note : The A register MAY not return 0, it is guaranteed though that it
will not return 12.
 
  CALLOS : 13
  Purpose: To read a register in the AUX_PORT
Entry : A = 13
X = register (0 to 15)
Y, c, z = undefined
  Exit : A = 0
Y = value read
X, c, z is preserved
  Note : See above


  Call : OS_SENDJOB
  CALLOS : 14 (OS 2.066+)
  Purpose: To give a job value back to the user
  Purpose: To start sensor type checking
  Entry : A = byte to give
  Entry : A = 14
X, Y, c, z = undefined
X, Y, c, z = undefined
  Exit : A, X, Y is preserved
  Exit : A = 1
c, z = undefined
Y = value read
  Note   : This will send either out to the serial port or place a byte in
X, c, z is preserved
jobout_bufFor internal calls this stores the value in jobout_buf, which
Note : A returns 1.  This flags the ADC routines to start checking the
is allocated 128 bytes, no bound checking is done so sending more than 128
sensorsAfter calling this routine, you should poll CALLOS 15 to check
bytes to OS_SENDJOB will cause corruption of some memory.
when all the sensors have been checked.


CALLOS : 15 (OS 2.066+)
Purpose: To check the status of sensor checking
Entry : A = 15
X, Y, c, z = undefined
Exit : A = status
XY = pointer to sensor types block
c, z is preserved
Note : A does not return 0, though it is guaranteed to not return 15.
  Status of 0 = checking finished and no futher check pending
  Status of 1 = Waiting for current ADC channel to finish converting
before switching sensor checking on
  Status of >127 = Checking sensor types
  The block pointed to by XY consists of 4 bytes, each byte
  contains the sensor type for the appropiate sensor, a type
  of 0 means no sensor present


OS_DECODEJOB
CALLOS : 16 (OS 2.069+)
 
  Purpose: To set the OS' irq mask for the VIA
JobCalls in practise are really only used by the user, JobCalls
  Entry : A = 16
tend not to call other calls at all, infact you shouldn't really call
X = EOR mask
another call unless you were called from the serial port. If you do need to
Y = AND mask
call another JobCall then you do it like this; with each call you have
c, z = undefined
associated with it a number, and the call might require some data or give
  Exit : A = 0
some data back or both. With the serial port this is simple, you just send
X = old mask value
data down and receive data as it is produced.  You can't do this with
Y = new mask value
internal calls, and some calls will not let you call them from inside the
c, z is preserved
controller, you HAVE to call them from outside. But for the ones which do
Note :
let you, you do it with two data blocks, one for input TO the call and one
for output FROM the call.  These are setup in a position in memory
(jobin_buf and jobout_buf), each one has a maximum of 128 bytes and this
should not be overflowed (OS_READJOB and OS_SENDJOB do NOT do any bound
checking). So to start with you place in the input block the data you want
to give the call (if any), call it, and then act on the data in the output
block (if any).  The OS call OS_DECODEJOB is the one which actually calls
the JobCall handler and eventually the actual routine, you tell the JobCall
handler which call you want by placing the JobCode in the A register.


So for OS_DECODEJOB:
CALLOS : 17 (OS 2.069+)
Purpose: To set the OS' irq mask for the ACIA
Entry : A = 17
X = EOR mask
Y = AND mask
c, z = undefined
Exit : A = 0
X = old mask value
Y = new mask value
c, z is preserved
Note :


  Call : OS_DECODEJOB
  CALLOS : 18 (OS 2.069+)
  Entry : A = JobCode
Purpose: To set the OS' irq mask for the ADC
X, Y, c, z = undefined
  Entry : A = 18
jobin_buf should contain any input data
X = EOR mask
  Exit : A = flag
Y = AND mask
X = number of bytes given back by routine
c, z = undefined
Y = undefined
  Exit : A = 0
c = status, if set JobCall does not exist
X = old mask value
z = status, if set JobCall cannot be called internally
Y = new mask value
jobout_buf contains any data sent from routine
c, z is preserved
Note :


A typical example would be:
CALLOS : 19 (OS 2.069+)
Purpose: To set the ACIA's ctrl register and OS' soft copy
Entry : A = 19
X = EOR mask
Y = AND mask
c, z = undefined
Exit : A = 0
X = old mask value
Y = new mask value
c, z is preserved
Note :


LDA #readhimem ; JobCode for ReadHimem
CALLOS : 20 (OS 2.069+)
JSR OS_DECODEJOB ; Call the JobCall handler
Purpose: To set the reset vector for battery back RAM support
LDX jobout_buf ; Read HIMEM from buffer LSB
Entry : A = 20
LDY jobout_buf+1 ; Read HIMEM from buffer MSB
                XY = address to set reset vector or 0 for read only
STX jobin_buf ; Place LSB in input buffer
c, z = undefined
STY jobin_buf+1 ; Place MSB in input buffer
Exit : A = 0
LDA #writelomem ; JobCode for WriteLomem
XY = old reset vector address
JSR OS_DECODEJOB ; Call the JobCall handler
c, z is preserved
Note : Sets the reset vector to address supplied and sets check bytes in
workspace.


which would read HIMEM, and then write LOMEM to be the same value, leaving
no spare memory left.


The reason why you should not call an internal call from another
Vectors
internal call is that the same buffers (jobin_buf and jobout_buf) would be
used for both calls, causing conflict between the two calls, or rather to
the originator of the primary call !  The only time it would be safe if is
if you have already got all the data you want from jobin_buf and haven't
placed any in the jobout_buf (it would be lost if you had done).


Some parts of the system are controlled by vectors, vectors also
exist to patch the normal operation of some parts of the system.  Vectors
are RAM pointers, which are called to point to the routine to use.  Being in
RAM this means that they can be altered by the user to allow the user to
modify the operation of part, or all, of a system call.  All the call
routines (eg.  OS_DECODEJOB, OS_READBYTE etc.) are called via a RAM vector.
There are vectors which the user patches to pick up unknown jobcodes, and to
service requests like NameCode/CodeName etc.


CallOS
The vectors are placed at address &200 in memory, each are 2 bytes
 
long.
This OS call does various calls which are completely linked to the
OS, everything from relocating code to reading and writing the hardware
ports.
 
As normal, a "function" request value is placed in the A register,
any extra data is put in the X and Y registers and a call to OS_CALLOS is
made, and data is passed back in X, Y, c and z.  The A register will return
0 for all routines except for calls 0 (deemed always to exist anyway), 12 (A
is reserved for future use), 14 (returns 1 instead) and 15 (12, 14 and 15 do
not return 12, 14, 15 in A though), if the routine passes back the same
value in A (the exception being call 0) as you gave it then that OS_CALLOS
function is not supported.


  CALLOS : 0
  brk_vec at address &200
  Purpose: Returns the OS version number
nmi_vec at address &202
  Entry : A = 0
irq_vec at address &204
X, Y, c, z = undefined
irq2_vec at address &206
Exit : A = Hardware release version
sendserial_vec at address &208
XY = OS version number
readserial_vec at address &20A
c, z is preserved
sendjob_vec at address &20C
  Note : Hardware version 0 is obsolete
readjob_vec at address &20E
  Hardware version 1 is SmartBox
decode_job_vec at address &210
  VIA at &8030
unknownjob_vec at address &212
  UART at &8010
extjob_vec at address &214
  ADC at &8000
centisec_vec at address &216
  AUX_PORT (inputs) at &8020
internal_vec at address &218
callos_vec at address &21A
printer_vec at address &21C
  reset_vec at address &21E
 
The brk_vec is called whenever there is a BRK error, which should
not occur under normal use, as the controller OS does not make use of BRK
errors.
The nmi_vec is called whenever there is a NMI request (not really
applicable, as the NMI line is not connected to anything).
The irq_vec is called whenever there is an interupt.
The irq2_vec is called whenever an unknown interupt is encountered,
ie. didnXt come from the controllerXs VIA, ACIA or ADC, or the interupt
from the VIA was not used by the OS.
The sendserial_vec is called when OS_SENDBYTE is called.
The readserial_vec is called when OS_READBYTE is called.
The sendjob_vec is called when OS_SENDJOB is called.
The readjob_vec is called when OS_READJOB is called.
The decodejob_vec is called when OS_DECODEJOB is called.  Note that
OS_DECODEJOB first calls a OS routine which sets a flag to say it's been
called internally, JobCode requests the OS gets from the serial port are
called via this vector, with a flag saying "from the serial port".
The unknownjob_vec is called whenever an unknown JobCode is
encountered.
The extendedjob_vec is called whenever the ExtendedJob is called,
the A register holds the extension value.
The centisecond_vec is called 100 times a second from the IRQ
routine from interupts off timer 1 of the VIA.  The OS has its pulsing
routines on the end of this.  Note that you should return with a RTS, not
RTI, you may also corrupt any of the registers.
The internal_vec is called when some information is needed from
various parts of the system, which includes the OS whch lies on the end of
this vector. An example is NameCode, which calls this vector to ask
everybody if they recognise the JobName in question.
The callos_vec is called when OS_CALLOS is called.
The printer_vec is called when OS_PRINTER is called.
The reset_vec is called when the OS gets a reset and the internal
check bytes flag the integrity of the RAM.  It is first called with C
cleared for everything to setup vectors and then called with C set for a
foreground "language" application to start up.  Use CALLOS 20 to set this
vector.
All vectors should be 'daisy chained', ie.  any routine which uses
any of them should store the current value in its own workspace and alter
the vector and at the end of the routine which services the vector should
jump to the original saved value, if this isnXt done the system may 'fall'
down.
unknown, extendjob and irq2 all point to (in the present OS) to a
routine which just returns.  brk in the current OS just resets the stack and
jumps back to the OS' main idle loop.
Irqs are reenabled before brk_vec is called


CALLOS : 1
Purpose: To claim a block of JobCodes
Entry : A = 1
X = number of JobCodes required
Y, c, z = undefined
Exit : A = 0
X = base JobCode, if 0, no big enough block available
Y = Job ID number, 0 if no big enough block
c = set if no big enough block
z is preserved
Note : This call claims JobCodes codes from the JobCode list.  JobCodes
are in the range 0 to 255, 0 is special and is the JobCall "Blank".  All the
OS ones are spaced out but an "application" can claim a block of JobCodes,
usually just the 1 call.  If there is not a contigous block big enough then
no JobCodes are allocated and the appropiate result is returned.  The Job ID
is from 0 to 255, 0 is unallocated, 1 is the OS calls, the rest are external
applications, each successful call increments the Job ID returned, meaning a
maximum of 255 successful calls (not important as their is not that many
free calls available !), the Job ID can be used to check whether a JobCall
request is yours.
CALLOS : 2
Purpose: To handle NameCode request for the application.
Entry : A = 2
XY = block pointing to block of JobNames
c, z = undefined
jobin_buf contains JobName (terminated by CR)
zero_gp3 = JobId (as returned by CALLOS 1)
Exit : A = 0
XY = undefined
z is preserved
if c = 1 (Name not found)
if c = 0 (Name found)
jobin_buf contains the JobCode
Note : Used via the internal_vec to convert from JobName to job code, the
table format is just a list of the JobNames (each one CR terminated)
terminated by a &ff


CALLOS : 3
Irq_vec
Purpose: To handle CodeName request for the application.
Entry : A = 3
XY = block pointing to block of JobNames
c, z = undefined
jobin_buf contains JobCode
zero_gp3 = job id (as returned by CALLOS 1)
Exit : A = 0
XY = undefined
z is preserved
if c = 1 (JobCode not found)
if c = 0 (JobCode found)
jobin contains JobName (terminated by CR)
Note : See above


  CALLOS : 4
This is called when the 65c02 receives an irq from a device. As the
  Purpose: To relocate code
65c02 signals brk and irq through the same cpu vector the OS first finds out
Entry : A = 4
which one it really was and calls either brk_vec or irq_vec. It also stores
XY = execution address (offset from start of code)
the A reg at address &a0, as the A reg is corrupted while it finds out which
c, z = undefined
vector to really call, any irq routine which has claimed the irq should
zero_gp1 = offset address of execution after relocation
restore the A reg from &a0 before returning via a RTI, else you should pass
zero_gp2 = offset address of relocation BitMap
on the call to the old vector owner.
zero_gp3 = length of code to relocate
 
  Exit : No exit, calls begining of code straight away
irq2_vec is called by the OS irq routine if it does not find a irq
Note : See later section on Relocation
it can service (or as been masked out) in as-good-as the same conditions as
irq_vec is called. The OS has a default irq2_vec handler of restoring A
from &a0 and doing a RTI.
 
 
Internal_vec and Unknownjob_vec


  CALLOS : 5
The main operation of extended JobCalls rely on patching some
Purpose: To "describe" the enviroment
vectors, two infact. These two are unknownjob_vec and internal_vec.
Entry : A = 5
unknownjob_vec is the one which the OS calls to actually tell everybody that
X, Y, c, z = undefined
a JobCall has been requested which it does not know about (the JobCall MUST
Exit : A = 0
have been claimed before hand with CALLOS, function 1, or else it is not
c, z is preserved
passed on)internal_vec is the one which the OS calls in request to a
XY = address of table, format as such:
NameCode or CodeName.
1st byte: number of bytes (ie. 14 currently)
byte 2+:
address of VIA
address of ACIA
address of ADC
address of AUX_PORT
address of jobs_status
address of jobin_buf
address of jobout_buf
  Note : It is preferred that this call is consulted first if direct use of
the hardware is going to be used to get the correct addresses, or use CALLOS
0 and decide what addresses/configuration to use from the Hardware version
number.


CALLOS : 6
The vectors are defined as thus:
Purpose: To read a register in the VIA
Entry : A = 6
X = register (0 to 15)
Y, c, z = undefined
Exit : A = 0
Y = value read
X, c, z is preserved
Note : The register number is not checked for range.  This call allows
access to the hardware without actually directly accessing the hardware
yourself.


  CALLOS : 7
  internal_vec
Purpose: To write to a register in the VIA
Entry : A = 7
X = register (0 to 15)
Y = value to write
c, z = undefined
Exit : A = 0
X, Y, c, z is preserved
Note : See above


  CALLOS : 8
  Function: 0
  Purpose: To read a register in the ACIA
  Purpose: Nothing
  Entry : A = 8
  Entry : A = 0
X = register (0 to 15)
X, Y, c, z = undefined
Y, c, z = undefined
  Exit : Leave everything as it is and exit straight away
  Exit : A = 0
  Note : Used for claimed calls
Y = value read
X, c, z is preserved
  Note : See above


  CALLOS : 9
  Function: 1
  Purpose: To write to a register in the ACIA
  Purpose: NameCode request
  Entry : A = 9
  Entry : A = 1
X = register (0 to 15)
X, Y, c, z = undefined
Y = value to write
jobin_buf contains JobName (CR terminated)
c, z = undefined
  Exit : if JobName recognised then
  Exit : A = 0
A = 0
X, Y, c, z is preserved
first byte of jobin_buf contains matched JobCode
  Note : See above
if JobName not recognised then
A = 1
X, Y, c, z = undefined
  Note :


  CALLOS : 10
  Function: 2
  Purpose: To read a register in the ADC
  Purpose: CodeName request
  Entry : A = 10
  Entry : A = 2
X = register (0 to 15)
X, Y, c, z = undefined
Y, c, z = undefined
jobin_buf contains JobCode
  Exit : A = 0
  Exit : if JobCode recognised then
Y = value read
A = 0
X, c, z is preserved
jobin_buf contains matched JobName (CR terminated)
  Note : See above
if Code not recognised then
A = 2
X, Y, c, z = undefined
  Note :


CALLOS : 11
All other calls (ie. not 0, 1 or 2 should be passed on straight
Purpose: To write to a register in the ADC
away for future expansion)
Entry : A = 11
X = register (0 to 15)
Y = value to write
c, z = undefined
  Exit : A = 0
X, Y, c, z is preserved
Note : See above


  CALLOS : 12
  unknownjob_vec
Purpose: To read a ADC channel
Entry : A = 12
X = channel number to read (0 to 3)
Y, c, z = undefined
Exit : A = reserved for future use
if c = 0 (8 bit reading)
Y = reading
X = undefined
if c = 1 (16 bit reading)
X = reading (LSB)
Y = reading (MSB)
z = undefined
Note : The A register MAY not return 0, it is guaranteed though that it
will not return 12.


CALLOS : 13
  Purpose: To pass on unknown JobCalls to the correct owner
  Purpose: To read a register in the AUX_PORT
  Entry : A = enviroment (0 = internal call, 1 = serial call)
  Entry : A = 13
Y = JobCode
X = register (0 to 15)
X = JOB ID of owner
Y, c, z = undefined
c, z = undefined
  Exit : A = 0
  Exit : if jobcode yours then
Y = value read
if wrong enviroment then
X, c, z is preserved
A = 1
  Note : See above
Y = 0
X, c, z = undefined
if right enviroment then
perform function
A = 0
Y = 0
X, c, z = undefined
if jobcode not yours then
pass on with registers unaltered
  Note :
 
 
Starting Up
 
The setup procedure for setting up extension job calls is to:


CALLOS : 14 (OS 2.066+)
a) Claim the number of JobCodes you want
Purpose: To start sensor type checking
if success:
Entry : A = 14
b) patch the internal_vec and unknownjob_vec vectors
X, Y, c, z = undefined
c) move LOMEM up to protect your program
Exit : A = 1
d) do anything else for initial startup you may want to do
Y = value read
e) return back, ready and waiting
X, c, z is preserved
 
Note : A returns 1.  This flags the ADC routines to start checking the
  This in code looks like:
sensors.  After calling this routine, you should poll CALLOS 15 to check
when all the sensors have been checked.


CALLOS : 15 (OS 2.066+)
execute
Purpose: To check the status of sensor checking
LDX #no_of_calls ; Number of JobCodes wanted
Entry : A = 15
LDA #1 ; CALLOS, function 1, Claim JobCodes
X, Y, c, z = undefined
JSR OS_CALLOS ; Call CALLOS
Exit : A = status
CPX #0 ; Check we were given some codes
XY = pointer to sensor types block
BEQ noinstall ; No, don�t bother installing ourself
c, z is preserved
STX call ; Store base address of JobCodes allocated
Note : A does not return 0, though it is guaranteed to not return 15.
STY job_id ; Store JOB ID given
  Status of 0 = checking finished and no futher check pending
  Status of 1 = Waiting for current ADC channel to finish converting
before switching sensor checking on
  Status of >127 = Checking sensor types
  The block pointed to by XY consists of 4 bytes, each byte
  contains the sensor type for the appropiate sensor, a type
  of 0 means no sensor present


CALLOS : 16 (OS 2.069+)
LDA internal_vec ; Get old internal_vec (LSB)
Purpose: To set the OS' irq mask for the VIA
STA ovec ; Store it
Entry : A = 16
LDA internal_vec+1 ; Get old internal_vec (MSB)
X = EOR mask
STA ovec+1 ; Store it
Y = AND mask
LDA #>internal_handle ; Our internal handler (LSB)
c, z = undefined
STA internal_vec ; Place it in the vector (LSB)
Exit : A = 0
LDA #<internal_handle ; Our internal handler (MSB)
X = old mask value
STA internal_vec+1 ; Place it in the vector (MSB)
Y = new mask value
c, z is preserved
Note :


CALLOS : 17 (OS 2.069+)
LDA unknownjob_vec ; Get old unknownjob_vec (LSB)
Purpose: To set the OS' irq mask for the ACIA
STA ovec2 ; Store it
Entry : A = 17
LDA unknownjob_vec+1 ; Get old unknownjob_vec (MSB)
X = EOR mask
STA ovec2+1 ; Store it
Y = AND mask
LDA #>job_handle ; Our unknown job handler (LSB)
c, z = undefined
STA unknownjob_vec ; Place it in the vector (LSB)
Exit : A = 0
LDA #<job_handle ; Our unknown job handler (MSB)
X = old mask value
STA unknownjob_vec+1 ; Place it in the vector (MSB)
Y = new mask value
c, z is preserved
Note :


CALLOS : 18 (OS 2.069+)
LDX #>WriteLomem ; Point to JobName
Purpose: To set the OS' irq mask for the ADC
LDY #<WriteLomem
Entry : A = 18
JSR findjob ; Find WriteLomem code
X = EOR mask
BEQ noinstall ; Doesn't exist !
Y = AND mask
LDX #>end_of_code ; End of code (LSB)
c, z = undefined
LDY #<end_of_code ; End of code (MSB)
Exit : A = 0
STX jobin_buf ; Place it in jobin_buf (LSB)
X = old mask value
STY jobin_buf+1 ; Place it in jobin_buf+1 (MSB)
Y = new mask value
JSR OS_DECODEJOB ; Call JobCall handler
c, z is preserved
 
Note :
noinstall
RTS ; End - return to OS


CALLOS : 19 (OS 2.069+)
findjob
Purpose: To set the ACIA's ctrl register and OS' soft copy
STX zero.gp1 ; Zero page
Entry : A = 19
STY zero.gp1+
X = EOR mask
LDY #0
Y = AND mask
findthisjoblo
c, z = undefined
LDA (zero.gp1),Y
Exit : A = 0
STA jobin.buf,Y ; Store name in jobin_buf
X = old mask value
INY
Y = new mask value
CMP #13
c, z is preserved
BNE findthisjoblo
Note :
LDA #3 ; NameCode
JSR OS.DECODEJOB ; Call NameCode
LDA jobout.buf ; Get returned value
RTS
 
WriteLomem STR "WriteLomem"


CALLOS : 20 (OS 2.069+)
This will setup the various vectors and memory pointers for the
Purpose: To set the reset vector for battery back RAM support
applicaion, the actual routines (internal_handle and job_handle) are fairly
Entry : A = 20
straight forward and simple:
                XY = address to set reset vector or 0 for read only
c, z = undefined
Exit : A = 0
XY = old reset vector address
c, z is preserved
Note : Sets the reset vector to address supplied and sets check bytes in
workspace.


internal_handle
CMP #1 : Is it function call 1 (NameCode) ?
BNE internal_handle_2 ; No, check for function 2
LDA job_id ; Get our JOB ID number
STA zero_gp3 ; Place it in zero_gp3 for CALLOS
LDX #>job_names ; Our Job name table (LSB)
LDY #<job_names ; Our Job name table (MSB)
LDA #2 ; CALLOS function 2
JSR OS_CALLOS ; Call CALLOS
BCC internal_yes : Found, go off and claim call
LDA #1 ; Not found, restore A
JMP (ovec) ; Call back to old vector


Vectors
internal_handle_2
 
CMP #2 ; Is it function call 2 (CodeName) ?
Some parts of the system are controlled by vectors, vectors also
BNE internal_handle_3 ; No, unknown, pass on
exist to patch the normal operation of some parts of the system.  Vectors
LDA job_id ; Get our JOB ID
are RAM pointers, which are called to point to the routine to use.  Being in
STA zero_gp3 ; Place it in zero_gp3 for CALLOS
RAM this means that they can be altered by the user to allow the user to
LDX #>job_names ; Our Job name table (LSB)
modify the operation of part, or all, of a system call.  All the call
LDY #<job_names ; Our Job name table (MSB)
routines (eg.  OS_DECODEJOB, OS_READBYTE etc.) are called via a RAM vector.
LDA #3 ; CALLOS function 3
There are vectors which the user patches to pick up unknown jobcodes, and to
JSR OS_CALLOS ; Call CALLOS
service requests like NameCode/CodeName etc.
BCC internal_yes ; Found, go off and claim call
LDA #2 ; Not found, restore A
JMP (ovec) ; Call back to old vector
 
internal_yes
LDA #0 ; We want to claim call for some reason


The vectors are placed at address &200 in memory, each are 2 bytes
internal_handle_3
long.
JMP (ovec) ; Call back to old vector


brk_vec at address &200
job_handle
nmi_vec at address &202
PHA ; Mark sure to preserve A
irq_vec at address &204
CPY call ; Check call wanted against base of ours
irq2_vec at address &206
BCC job_handle_no ; Less than our base, so definately not ours
sendserial_vec at address &208
TYA
readserial_vec at address &20A
SBC #no_of_calls ; Subtract number of calls we have
sendjob_vec at address &20C
CMP call ; Now compare with base
readjob_vec at address &20E
BCC job_handle2 ; Yes, one of ours
decode_job_vec at address &210
unknownjob_vec at address &212
extjob_vec at address &214
centisec_vec at address &216
internal_vec at address &218
callos_vec at address &21A
printer_vec at address &21C
reset_vec at address &21E


The brk_vec is called whenever there is a BRK error, which should
job_handle.no
not occur under normal use, as the controller OS does not make use of BRK
PLA ; Restore A
errors.
JMP (ovec2) ; Call back to old vector
The nmi_vec is called whenever there is a NMI request (not really
 
applicable, as the NMI line is not connected to anything).
job_handle2
The irq_vec is called whenever there is an interupt.
TYA
The irq2_vec is called whenever an unknown interupt is encountered,
SEC
ie.  didnXt come from the controllerXs VIA, ACIA or ADC, or the interupt
SBC call ; Subtract our base from call
from the VIA was not used by the OS.
ASL A ; Times by 2 for offset into table
The sendserial_vec is called when OS_SENDBYTE is called.
TAY ; Place in Y
The readserial_vec is called when OS_READBYTE is called.
LDA job_run_table,Y ; Get LSB of routine
The sendjob_vec is called when OS_SENDJOB is called.
STA zero_gp1 ; Store it for indirect jump (LSB)
The readjob_vec is called when OS_READJOB is called.
LDA job_run_table+1,Y ; Get MSB of routine
The decodejob_vec is called when OS_DECODEJOB is called.  Note that
STA zero_gp1+1 ; Store it for indirect jump (MSB)
OS_DECODEJOB first calls a OS routine which sets a flag to say it's been
PLA ; Restore A
called internally, JobCode requests the OS gets from the serial port are
JMP (zero_gp1) ; Call our relevant routine
called via this vector, with a flag saying "from the serial port".
 
The unknownjob_vec is called whenever an unknown JobCode is
job_names ; List of our JobNames
encountered.
STR "TestJob" ; Test job name
The extendedjob_vec is called whenever the ExtendedJob is called,
DFB &FF ; &FF - marks end of table
the A register holds the extension value.
 
The centisecond_vec is called 100 times a second from the IRQ
job_run_table ; List of routine addresses to match Jobs
routine from interupts off timer 1 of the VIA.  The OS has its pulsing
DFW job_TestJob ; Test job routine
routines on the end of this.  Note that you should return with a RTS, not
 
RTI, you may also corrupt any of the registers.
 
The internal_vec is called when some information is needed from
Relocation
various parts of the system, which includes the OS whch lies on the end of
 
this vector.  An example is NameCode, which calls this vector to ask
The relocation method used will not do a complete relocation, ie.
everybody if they recognise the JobName in question.
from ANY address to ANY address.  The only constraint on the addresses is
The callos_vec is called when OS_CALLOS is called.
that they must be the same offset in the page, which means, &2602 and &3402
The printer_vec is called when OS_PRINTER is called.
is alright but &2602 and &3455 is not, ie.  the difference between the two
The reset_vec is called when the OS gets a reset and the internal
addresses must be a multiple of 256 bytes, as only the MSB is altered, this
check bytes flag the integrity of the RAM.  It is first called with C
should not cause any real problems. What is meant by the two addresses is
cleared for everything to setup vectors and then called with C set for a
the address the code was originally assembled at and the address it wants
foreground "language" application to start upUse CALLOS 20 to set this
relocating to.  Because of this programs must be multiples of 256 bytes, ie.  
vector.
the low byte of LOMEM must ALWAYS be 0, your program should be padded out.
All vectors should be 'daisy chained', ie. any routine which uses
any of them should store the current value in its own workspace and alter
the vector and at the end of the routine which services the vector should
jump to the original saved value, if this isnXt done the system may 'fall'
down.
unknown, extendjob and irq2 all point to (in the present OS) to a
routine which just returns. brk in the current OS just resets the stack and
jumps back to the OS' main idle loop.
Irqs are reenabled before brk_vec is called


The relocation mechanism works by having a table, and to each byte
of code there is one BIT, if unset it means the byte of code should not be
relocated, else it should be, thus the BitMap table is 8 times smaller than
the code.  To generate the BitMap you have to assemble the code at two
different places, and compare both sets of assembled code against each
other, the differences are where the code is to be relocated.  The code
which is sent up to the controller is assumed to be assembled at address
&100, so the first code should be assembled at &100, the other one should be
assembled at another address, say &600, and the first one be sent up with
the BitMap.  A program is availble for the Elk/Beeb/Arc with 65Tube/Mac with
BBC BASIC to compare the two files and create the BitMap.


Irq_vec
All the program has to do to relocate itself is execute this bit of
 
code first of all: (address is the base address of the program, ie.  &100
This is called when the 65c02 receives an irq from a device.  As the
for the downloaded version)
65c02 signals brk and irq through the same cpu vector the OS first finds out
which one it really was and calls either brk_vec or irq_vec.  It also stores
the A reg at address &a0, as the A reg is corrupted while it finds out which
vector to really call, any irq routine which has claimed the irq should
restore the A reg from &a0 before returning via a RTI, else you should pass
on the call to the old vector owner.


irq2_vec is called by the OS irq routine if it does not find a irq
execcall
it can service (or as been masked out) in as-good-as the same conditions as
LDA #>(execute-address) ; Final execution address offset (LSB)
irq_vec is called.  The OS has a default irq2_vec handler of restoring A
STA zero_gp1
from &a0 and doing a RTI.
LDA #<(execute-address) ; Final execution address offset (MSB)
STA zero_gp1+1


LDA #>(BitMap-address) ; BitMap address offset (LSB)
STA zero_gp2
LDA #<(BitMap-address) ; BitMap address offset (MSB)
STA zero_gp2+1


Internal_vec and Unknownjob_vec
LDA #>(execcall-address); Length of data to relocate (LSB)
STA zero_gp3
LDA #<(execcall-address); Length of data to relocate (MSB)
STA zero_gp3+1


The main operation of extended JobCalls rely on patching some
TXA ; Execcall REAL address
vectors, two infactThese two are unknownjob_vec and internal_vec.
SEC
unknownjob_vec is the one which the OS calls to actually tell everybody that
SBC #>(execcall-address); Subrtract length
a JobCall has been requested which it does not know about (the JobCall MUST
TAX ; Put it back in X
have been claimed before hand with CALLOS, function 1, or else it is not
TYA ; Ditto for MSB
passed on).  internal_vec is the one which the OS calls in request to a
SBC #<(execcall-address)
NameCode or CodeName.
TAY
LDA #4 ; CALLOS function for Relocate
JMP OS_CALLOS ; Call CALLOS
 
BitMap ; BitMap start
 
This will relocate the code and automatically call the execution
address, which should be the initialisation code (iesetup internal_vec
and unknownjob_vec etc.).  "address" is the address the code is started to
assemble at, &100 for the version to be uploaded.  The upload routine should
pass in X and Y (via ExecuteCode) the address of the start of execcall, this
means that the routine can tell where it is in memory, subtracting the
length gives the start of the code to relocate.


The vectors are defined as thus:


internal_vec
Layout Summary


Function: 0
The layout of a full application should be like this by now:
Purpose: Nothing
Entry : A = 0
X, Y, c, z = undefined
Exit : Leave everything as it is and exit straight away
Note : Used for claimed calls


Function: 1
start of code
Purpose: NameCode request
program job codes, data etc.
Entry : A = 1
internal handler
X, Y, c, z = undefined
job handler
jobin_buf contains JobName (CR terminated)
end of code
Exit : if JobName recognised then
initialisation (setup internal etc.)
A = 0
end of code to be relocated
first byte of jobin_buf contains matched JobCode
execution (relocation)
if JobName not recognised then
relocation BitMap
A = 1
 
X, Y, c, z = undefined
Because of memory space the initialisation code should be placed
Note :
outside the reserved memory limit of the program, as it is only called once
and not needed again.


Function: 2
Purpose: CodeName request
Entry : A = 2
X, Y, c, z = undefined
jobin_buf contains JobCode
Exit : if JobCode recognised then
A = 0
jobin_buf contains matched JobName (CR terminated)
if Code not recognised then
A = 2
X, Y, c, z = undefined
Note :


All other calls (ie.  not 0, 1 or 2 should be passed on straight
Other miscellaneous calls:
away for future expansion)


unknownjob_vec
Call : OS_PRINTER
 
Purpose : To place a character in the printer buffer
Purpose: To pass on unknown JobCalls to the correct owner
Entry : A = character to send
Entry : A = enviroment (0 = internal call, 1 = serial call)
  X, Y, c, z = undefined
Y = JobCode
Exit : A, X, Y is preserved
X = JOB ID of owner
  if c = 0 (printed)
c, z = undefined
The printer is on and the character was inserted
Exit : if jobcode yours then
  if c = 1 (not printed)
if wrong enviroment then
The printer is off and the character was forgotten
A = 1
  z = undefined
Y = 0
Note : The character goes into the printer buffer and will be
X, c, z = undefined
actually sent to the printer when the printer asks for it. If the printer
if right enviroment then
was not awake to begin with, an attempt is made to wake it up, thus
perform function
meaning that in some circumstances when the printer wasn't ready a
A = 0
character may be lost, this cannot be helped. If the printer is off (via
Y = 0
the BCD switch) then the character will be forgotten as the Printer Buffer
X, c, z = undefined
is inactive.
if jobcode not yours then
pass on with registers unaltered
Note :




Starting Up
  The module is located at &8000 in memory, and should be a EPROM placed
 
in the second ROM socket in the controller (ie. the top RAM socket). This
The setup procedure for setting up extension job calls is to:
module is primary designed to turn the Controller into a dedicated task,
making it perform some function from switch on. It could also be used to
add more permant JobCalls to the Controller, in the process taking up much
less RAM), but because of there being only one Module present this feature
is limited, and is much more suited to a collection of the user's own
personal routines.
  For the OS to recognise a module as being in place the text "Module" is
looked for, the layout of a module should be like this (starting at
&8000):


a) Claim the number of JobCodes you want
Offset value comment
if success:
0 "Module" The OS checks for this string
b) patch the internal_vec and unknownjob_vec vectors
6 0 End of check string
c) move LOMEM up to protect your program
7 Language entry Address of 'Language' entry
d) do anything else for initial startup you may want to do
9 Service entry Address of 'Service' entry
e) return back, ready and waiting
11 "<Module Title>" Module title
0 End of Module title


  This in code looks like:
"<Module part>" Part Module Title
0 End of Part Module Title
"x.xx" Part version number


execute
&FF End of parts list
LDX #no_of_calls ; Number of JobCodes wanted
LDA #1 ; CALLOS, function 1, Claim JobCodes
JSR OS_CALLOS ; Call CALLOS
CPX #0 ; Check we were given some codes
BEQ noinstall ; No, don�t bother installing ourself
STX call ; Store base address of JobCodes allocated
STY job_id ; Store JOB ID given


LDA internal_vec ; Get old internal_vec (LSB)
  The language entry is a 2 byte value holding the address of the language
STA ovec ; Store it
entry routine, this is called on reset, if the BCD is set correctly. A
LDA internal_vec+1 ; Get old internal_vec (MSB)
value of less than &8000 means there is no valid language entry and any
STA ovec+1 ; Store it
language calls are ignored. If a RTS is made the OS carries on as it would
LDA #>internal_handle ; Our internal handler (LSB)
have if there wasn't a language call.
STA internal_vec ; Place it in the vector (LSB)
LDA #<internal_handle ; Our internal handler (MSB)
STA internal_vec+1 ; Place it in the vector (MSB)


LDA unknownjob_vec ; Get old unknownjob_vec (LSB)
  The service entry is a 2 byte value holding the address of the service
STA ovec2 ; Store it
entry routine, this is called at various appropiate moments with service
LDA unknownjob_vec+1 ; Get old unknownjob_vec (MSB)
numbers in A. A list of current calls is thus:
STA ovec2+1 ; Store it
LDA #>job_handle ; Our unknown job handler (LSB)
STA unknownjob_vec ; Place it in the vector (LSB)
LDA #<job_handle ; Our unknown job handler (MSB)
STA unknownjob_vec+1 ; Place it in the vector (MSB)


LDX #>WriteLomem ; Point to JobName
  0 - Not used
LDY #<WriteLomem
  1 - Unknown Job Code
JSR findjob ; Find WriteLomem code
  2 - Centisecond call
BEQ noinstall ; Doesn't exist !
  3 - Irq2 (unknown IRQ)
LDX #>end_of_code ; End of code (LSB)
  4 - internal_vec call (exit with A=0 to claim)
LDY #<end_of_code ; End of code (MSB)
254 - RESET
STX jobin_buf ; Place it in jobin_buf (LSB)
255 - BRK
STY jobin_buf+1 ; Place it in jobin_buf+1 (MSB)
JSR OS_DECODEJOB ; Call JobCall handler


noinstall
  On exit all registers and flags should be preserved. For call 1 the
RTS ; End - return to OS
unknown job value is held in the Y register, the enviroment is held in the
X register, on exit, X should contain the flag, as A does from the exit of
unknownjob_vec. For the rest of calls, X and Y are undefined.


findjob
  For both Service and Language entry points, a value of 0 means "there is
STX zero.gp1 ; Zero page
no valid routine" and the module isn't called for that type of entry.
STY zero.gp1+
LDY #0
findthisjoblo
LDA (zero.gp1),Y
STA jobin.buf,Y ; Store name in jobin_buf
INY
CMP #13
BNE findthisjoblo
LDA #3 ; NameCode
JSR OS.DECODEJOB ; Call NameCode
LDA jobout.buf ; Get returned value
RTS


WriteLomem STR "WriteLomem"


This will setup the various vectors and memory pointers for the
applicaion, the actual routines (internal_handle and job_handle) are fairly
straight forward and simple:


internal_handle
  Finally a full implementation of a JobCall, from start to finish.
CMP #1 : Is it function call 1 (NameCode) ?
BNE internal_handle_2 ; No, check for function 2
LDA job_id ; Get our JOB ID number
STA zero_gp3 ; Place it in zero_gp3 for CALLOS
LDX #>job_names ; Our Job name table (LSB)
LDY #<job_names ; Our Job name table (MSB)
LDA #2 ; CALLOS function 2
JSR OS_CALLOS ; Call CALLOS
BCC internal_yes : Found, go off and claim call
LDA #1 ; Not found, restore A
JMP (ovec) ; Call back to old vector


internal_handle_2
DIM data% &1000
CMP #2 ; Is it function call 2 (CodeName) ?
:
BNE internal_handle_3 ; No, unknown, pass on
no_of_calls = 1
LDA job_id ; Get our JOB ID
:
STA zero_gp3 ; Place it in zero_gp3 for CALLOS
VIA = &E030
LDX #>job_names ; Our Job name table (LSB)
ACIA = &E010
LDY #<job_names ; Our Job name table (MSB)
ADC = &E000
LDA #3 ; CALLOS function 3
AUX_PORT = &E020
JSR OS_CALLOS ; Call CALLOS
brk_vec = &200
BCC internal_yes ; Found, go off and claim call
nmi_vec = &202
LDA #2 ; Not found, restore A
irq_vec = &204
JMP (ovec) ; Call back to old vector
irq2_vec = &206
 
sendserial_vec = &208
internal_yes
readserial_vec = &20A
LDA #0 ; We want to claim call for some reason
sendjob_vec = &20C
 
readjob_vec = &20E
internal_handle_3
decodejob_vec = &210
JMP (ovec) ; Call back to old vector
unknownjob_vec = &212
 
extjob_vec = &214
job_handle
centisec_vec = &216
PHA ; Mark sure to preserve A
internal_vec = &218
CPY call ; Check call wanted against base of ours
callos_vec = &21A
BCC job_handle_no ; Less than our base, so definately not ours
printer_vec = &21C
TYA
zero_gp1 = 0
SBC #no_of_calls ; Subtract number of calls we have
zero_gp2 = 2
CMP call ; Now compare with base
zero_gp3 = 4
BCC job_handle2 ; Yes, one of ours
zero_gp4 = 6
 
zero_gp5 = 8
job_handle.no
zero_gp6 = 10
PLA ; Restore A
zero_gp7 = 12
JMP (ovec2) ; Call back to old vector
zero_gp8 = 14
 
zero_gp9 = 16
job_handle2
zero_gp10 = 18
TYA
user_reserved = &70
SEC
irq_A = &A0
SBC call ; Subtract our base from call
fcount = &A1
ASL A ; Times by 2 for offset into table
RAM_size = &A3
TAY ; Place in Y
jobout_buf = &400
LDA job_run_table,Y ; Get LSB of routine
jobin_buf = &480
STA zero_gp1 ; Store it for indirect jump (LSB)
OS_PRINTER = &FFB9
LDA job_run_table+1,Y ; Get MSB of routine
OS_CALLOS = &FFBC
STA zero_gp1+1 ; Store it for indirect jump (MSB)
OS_SENDBYTE = &FFBF
PLA ; Restore A
OS_READBYTE = &FFC2
JMP (zero_gp1) ; Call our relevant routine
OS_SENDJOB = &FFC5
 
OS_READJOB = &FFC8
job_names ; List of our JobNames
OS_DECODEJOB = &FFCB
STR "TestJob" ; Test job name
:
DFB &FF ; &FF - marks end of table
FOR create=1 TO 2
:
FOR pass%=4 TO 7 STEP 3
P%=&100*create : O%=data%
[OPT pass%
:
.job_DemoJob
CMP #1
BEQ DemoJob_go
LDA #1
LDY #0
RTS
/
.DemoJob_go


job_run_table ; List of routine addresses to match Jobs
DFW job_TestJob ; Test job routine




Relocation


The relocation method used will not do a complete relocation, ie.
:
from ANY address to ANY address. The only constraint on the addresses is
.internal_handle
that they must be the same offset in the page, which means, &2602 and &3402
CMP #1 ; Is it function call 1 (NameCode) ?
is alright but &2602 and &3455 is not, iethe difference between the two
BNE internal_handle_2 ; No, check for function 2
addresses must be a multiple of 256 bytes, as only the MSB is altered, this
/
should not cause any real problemsWhat is meant by the two addresses is
  LDA job_id ; Get our JOB ID number
the address the code was originally assembled at and the address it wants
STA zero_gp3 ; Place it in zero_gp3 for CALLOS
relocating to.  Because of this programs must be multiples of 256 bytes, ie.  
LDX #job_names MOD 256 ; Our Job name table (LSB)
the low byte of LOMEM must ALWAYS be 0, your program should be padded out.
LDY #job_names DIV 256 ; Our Job name table (MSB)
 
LDA #2 ; CALLOS function 2
The relocation mechanism works by having a table, and to each byte
JSR OS_CALLOS ; Call CALLOS
of code there is one BIT, if unset it means the byte of code should not be
BCC internal_yes ; Found, go off and claim call
relocated, else it should be, thus the BitMap table is 8 times smaller than
LDA #1 ; Not found, restore A
the codeTo generate the BitMap you have to assemble the code at two
JMP (ovec) ; Call back to old vector
different places, and compare both sets of assembled code against each
/
other, the differences are where the code is to be relocatedThe code
.internal_handle_2
which is sent up to the controller is assumed to be assembled at address
  CMP #2 ; Is it function call 2 (CodeName) ?
&100, so the first code should be assembled at &100, the other one should be
BNE internal_handle_3 ; No, unknown, pass on
assembled at another address, say &600, and the first one be sent up with
/
the BitMap. A program is availble for the Elk/Beeb/Arc with 65Tube/Mac with
LDA job_id ; Get our JOB ID
BBC BASIC to compare the two files and create the BitMap.
STA zero_gp3 ; Place it in zero_gp3 for CALLOS
 
LDX #job_names MOD 256 ; Our Job name table (LSB)
All the program has to do to relocate itself is execute this bit of
LDY #job_names DIV 256 ; Our Job name table (MSB)
code first of all: (address is the base address of the program, ie. &100
LDA #3 ; CALLOS function 3
for the downloaded version)
JSR OS_CALLOS ; Call CALLOS
 
BCC internal_yes ; Found, go off and claim call
execcall
LDA #2 ; Not found, restore A
LDA #>(execute-address) ; Final execution address offset (LSB)
JMP (ovec) ; Call back to old vector
STA zero_gp1
/
LDA #<(execute-address) ; Final execution address offset (MSB)
.internal_yes
STA zero_gp1+1
LDA #0 ; We want to claim call for some reason
 
  /
LDA #>(BitMap-address) ; BitMap address offset (LSB)
.internal_handle_3
STA zero_gp2
JMP (ovec) ; Call back to old vector
LDA #<(BitMap-address) ; BitMap address offset (MSB)
:
STA zero_gp2+1
.job_handle
 
PHA ; Make sure to preserve A
LDA #>(execcall-address); Length of data to relocate (LSB)
  CPY call ; Check call wanted against base of ours
STA zero_gp3
BCC job_handle_no ; Less than our base, so definately not ours
LDA #<(execcall-address); Length of data to relocate (MSB)
TYA
STA zero_gp3+1
SBC #no_of_calls ; Subtract number of calls we have
 
CMP call ; Now compare with base
TXA ; Execcall REAL address
BCC job_handle2 ; Yes, one of ours
SEC
/
SBC #>(execcall-address); Subrtract length
.job_handle.no
TAX ; Put it back in X
PLA ; Restore A
TYA ; Ditto for MSB
JMP (ovec2) ; Call back to old vector
SBC #<(execcall-address)
/
TAY
.job_handle2
LDA #4 ; CALLOS function for Relocate
TYA
JMP OS_CALLOS ; Call CALLOS
SEC
 
SBC call ; Subtract our base from call
BitMap ; BitMap start
ASL A ; Times by 2 for offset into table
 
TAY ; Place in Y
This will relocate the code and automatically call the execution
LDA job_run_table,Y ; Get LSB of routine
address, which should be the initialisation code (ie. setup internal_vec
STA zero_gp1 ; Store it for indirect jump (LSB)
and unknownjob_vec etc.). "address" is the address the code is started to
LDA job_run_table+1,Y ; Get MSB of routine
assemble at, &100 for the version to be uploaded. The upload routine should
STA zero_gp1+1 ; Store it for indirect jump (MSB)
pass in X and Y (via ExecuteCode) the address of the start of execcall, this
PLA ; Restore A
means that the routine can tell where it is in memory, subtracting the
JMP (zero_gp1) ; Call our relevant routine
length gives the start of the code to relocate.
:
 
.job_names ; List of our JobNames
 
  EQUS "DemoJob":EQUB 13 ; Test job name
Layout Summary
/
 
EQUB &FF ; &FF - marks end of table
The layout of a full application should be like this by now:
:
 
.job_run_table ; List of routine addresses to match Jobs
start of code
EQUW job_DemoJob ; Test job routine
program job codes, data etc.
:
internal handler
.call  : EQUB 0 ; Store for our JobCode base
job handler
.job_id : EQUB 0 ; Store for our JobId
end of code
.ovec  : EQUW 0 ; Store for old internal_vec value
initialisation (setup internal etc.)
.ovec2 : EQUW 0 ; Store for old unknownjob_vec value
end of code to be relocated
:
execution (relocation)
EQUS STRING$(&100-(P% MOD 256),CHR$0)
relocation BitMap
.end_of_code ; end_of_code should be on page boundry
 
:
Because of memory space the initialisation code should be placed
.execute
outside the reserved memory limit of the program, as it is only called once
LDX #no_of_calls ; Number of JobCodes wanted
and not needed again.
LDA #1 ; CALLOS, function 1, Claim JobCodes
 
JSR OS_CALLOS ; Call CALLOS
 
STX call ; Store base address of JobCodes allocated
Other miscellaneous calls:
STY job_id ; Store JOB ID given
 
CPX #0 ; Check we were given some codes
Call : OS_PRINTER
BEQ noinstall ; No, don't bother installing ourself
Purpose : To place a character in the printer buffer
/
Entry : A = character to send
LDA internal_vec ; Get old internal_vec (LSB)
  X, Y, c, z = undefined
STA ovec ; Store it
Exit : A, X, Y is preserved
LDA internal_vec+1 ; Get old internal_vec (MSB)
  if c = 0 (printed)
STA ovec+1 ; Store it
The printer is on and the character was inserted
LDA #internal_handle MOD 256 ; Our internal handler (LSB)
  if c = 1 (not printed)
STA internal_vec ; Place it in the vector (LSB)
The printer is off and the character was forgotten
LDA #internal_handle DIV 256 ; Our internal handler (MSB)
  z = undefined
STA internal_vec+1 ; Place it in the vector (MSB)
Note : The character goes into the printer buffer and will be
  /
actually sent to the printer when the printer asks for it. If the printer
LDA unknownjob_vec ; Get old unknownjob_vec (LSB)
was not awake to begin with, an attempt is made to wake it up, thus
STA ovec2 ; Store it
meaning that in some circumstances when the printer wasn't ready a
LDA unknownjob_vec+1 ; Get old unknownjob_vec (MSB)
character may be lost, this cannot be helped. If the printer is off (via
STA ovec2+1 ; Store it
the BCD switch) then the character will be forgotten as the Printer Buffer
LDA #job_handle MOD 256 ; Our unknown job handler (LSB)
is inactive.
STA unknownjob_vec ; Place it in the vector (LSB)
 
LDA #job_handle DIV 256 ; Our unknown job handler (MSB)
 
STA unknownjob_vec+1 ; Place it in the vector (MSB)
  The module is located at &8000 in memory, and should be a EPROM placed
/
in the second ROM socket in the controller (ie. the top RAM socket). This
LDX #end_of_code MOD 256 ; End of code (LSB)
module is primary designed to turn the Controller into a dedicated task,
LDY #end_of_code DIV 256 ; End of code (MSB)
making it perform some function from switch on. It could also be used to
STX jobin_buf ; Place it in jobin_buf (LSB)
add more permant JobCalls to the Controller, in the process taking up much
  STY jobin_buf+1 ; Place it in jobin_buf+1 (MSB)
less RAM), but because of there being only one Module present this feature
LDA #65 ; JobCode for WriteLomem
is limited, and is much more suited to a collection of the user's own
JSR OS_DECODEJOB ; Call JobCall handler
personal routines.
/
  For the OS to recognise a module as being in place the text "Module" is
.noinstall
looked for, the layout of a module should be like this (starting at
RTS ; End - return to OS
&8000):
:
 
.execcall
Offset value comment
LDA #>(execute-address) ; Final execution address offset (LSB)
0 "Module" The OS checks for this string
STA zero_gp1
  6 0 End of check string
LDA #<(execute-address) ; Final execution address offset (MSB)
7 Language entry Address of 'Language' entry
STA zero_gp1+1
  9 Service entry Address of 'Service' entry
/
11 "<Module Title>" Module title
LDA #>(BitMap-address) ; BitMap address offset (LSB)
0 End of Module title
STA zero_gp2
 
LDA #<(BitMap-address) ; BitMap address offset (MSB)
"<Module part>" Part Module Title
STA zero_gp2+1
0 End of Part Module Title
/
"x.xx" Part version number
LDA #>(execcall-address) ; Length of data to relocate (LSB)
 
STA zero_gp3
&FF End of parts list
LDA #<(execcall-address) ; Length of data to relocate (MSB)
 
STA zero_gp3+1
  The language entry is a 2 byte value holding the address of the language
/
entry routine, this is called on reset, if the BCD is set correctly. A
TXA ; Execcall REAL address
value of less than &8000 means there is no valid language entry and any
SEC
language calls are ignored. If a RTS is made the OS carries on as it would
SBC #>(execcall-address) ; Subrtract length
have if there wasn't a language call.
TAX ; Put it back in X
TYA ; Ditto for MSB
SBC #<(execcall-address)
TAY
LDA #4 ; CALLOS function for Relocate
JMP OS_CALLOS ; Call CALLOS
/
.BitMap ; BitMap start
:
  ]
  NEXT
  :
OSCLI "SAVE code"+STR$create+" "+STR$~data%+" "+STR$~O%
  :
  NEXT</nowiki>


  The service entry is a 2 byte value holding the address of the service
== Changelog ==
entry routine, this is called at various appropiate moments with service
numbers in A. A list of current calls is thus:


  0 - Not used
<nowiki>bob 071 Fixed MotorForward bug (%10)
  1 - Unknown Job Code
bob 072 CallOS 14 now checks current adc.owner status
  2 - Centisecond call
jim 072.d1 * Rewrite for new hardware/processor *
  3 - Irq2 (unknown IRQ)
jim 072.d1 Moved vectors/workspace to &440
  4 - internal_vec call (exit with A=0 to claim)
jim 072.d1 Moved rs.inp.buf to &500
254 - RESET
jim 072.d1 Moved jobout.buf to &600
255 - BRK
jim 072.d1 Moved jobin.buf to &680
 
jim 072.d1 Moved jobs.status to &700
  On exit all registers and flags should be preserved. For call 1 the
jim 072.d1 Added irq.X (&A4)
unknown job value is held in the Y register, the enviroment is held in the
jim 072.d1 Added irq.Y (&A5)
X register, on exit, X should contain the flag, as A does from the exit of
jim 072.d1 Removed short detection code
unknownjob_vec. For the rest of calls, X and Y are undefined.
jim 072.d1 Voided CallOS' 6,7,8,9,10,11,13,16,17,18,19
 
jim 072.d1 Removed JobCalls ReadADCReg,WriteADCReg
  For both Service and Language entry points, a value of 0 means "there is
jim 072.d1 Removed JobCalls ReadACIAReg,WriteACIAReg
no valid routine" and the module isn't called for that type of entry.
jim 072.d1 Removed JobCalls ReadVIAReg,WriteVIAReg,SetVIAHigh,SetVIALow
 
jim 072.d1 Added CallOS 21 Write Outputs
 
jim 072.d1 Added CallOS 22 Read Outputs added
 
jim 072.d1 Added CallOS 23 Read Inputs added
  Finally a full implementation of a JobCall, from start to finish.
jim 072.d1 Added CallOS 24 Write Motors
 
jim 072.d1 Added CallOS 25 Read Motors
DIM data% &1000
jim 072.d1 Added CallOS 26 Read Keypad
:
jim 072.d1 Expanded CallOS 5 information
no_of_calls = 1
jim 072.d1 Added JobCall IdentSystem to return CallOS 5 info
:
jim 072.d1 JobCalls now use appropriate CallOS'
VIA = &E030
jim 072.d1 OS.READBYTE no longer preserves A on CS
ACIA = &E010
jim 072.d1 irq.vec,irq2.vec,nmi.vec now redundant
ADC = &E000
jim 072.d1 Renamed JobCall DownloadData to DownloadData38
AUX_PORT = &E020
jim 072.d1 Renamed JobCall UploadData to UploadData38
brk_vec = &200
jim 072.d1 Renamed JobCall ExecuteCode to ExecuteCode38
nmi_vec = &202
jim 072.d1 Renamed JobCall ReadByte to ReadByte38
irq_vec = &204
jim 072.d1 Renamed JobCall StoreByte to StoreByte38
irq2_vec = &206
jim 072.d1 JobCall ForcedADCRead now checks adc.owner first
sendserial_vec = &208
jim 072.d3 Added CallOS 27 Write printer
readserial_vec = &20A
jim 072.d3 Added CallOS 28 Read printer
sendjob_vec = &20C
jim 072.d3 Added CallOS 29 Write RTC Reg
readjob_vec = &20E
jim 072.d3 Added CallOS 30 Read RTC Reg
decodejob_vec = &210
jim 072.d3 Added CallOS 31 Write RTC string
unknownjob_vec = &212
jim 072.d3 Added CallOS 32 Read RTC string
extjob_vec = &214
jim 072.d3 Added CallOS 33 Write RTC bcd
centisec_vec = &216
jim 072.d3 Added CallOS 34 Read RTC bcd
internal_vec = &218
jim 072.d3 Added CallOS 35 Write LCD Reg
callos_vec = &21A
jim 072.d3 Added CallOS 36 Read LCD Reg
printer_vec = &21C
jim 072.d3 Added JobCall WritePrinter
zero_gp1 = 0
jim 072.d3 Added JobCall ReadPrinter
zero_gp2 = 2
jim 072.d3 Added JobCall PrintChar
zero_gp3 = 4
jim 072.d3 Added JobCall PrintStreamZ
zero_gp4 = 6
jim 072.d3 Added JobCall PrintStream
zero_gp5 = 8
jim 072.d3 Added JobCall PrintServer
zero_gp6 = 10
jim 072.d3 Added JobCall WriteRTCReg
zero_gp7 = 12
jim 072.d3 Added JobCall ReadRTCReg
zero_gp8 = 14
jim 072.d3 Added JobCall WriteRTC
zero_gp9 = 16
jim 072.d3 Added JobCall ReadRTC
zero_gp10 = 18
jim 072.d3 Added JobCall WriteRTCbcd
user_reserved = &70
jim 072.d3 Added JobCall ReadRTCbcd
irq_A = &A0
jim 072.d3 Added JobCall WriteLCDReg
fcount = &A1
jim 072.d3 Added JobCall ReadLCDReg
RAM_size = &A3
jim 072.d3 Added CallOS 37 Write Power ctrl
jobout_buf = &400
jim 072.d3 Added CallOS 38 Read Power ctrl
jobin_buf = &480
jim 072.d3 Implemented OS.PRINTER
OS_PRINTER = &FFB9
jim 072.d3 Added JobCall PatchMF (MotorForward)
OS_CALLOS = &FFBC
jim 072.d3 Changed irq.vec to int0.vec
OS_SENDBYTE = &FFBF
jim 072.d3 Changed irq2.vec to int1.vec
OS_READBYTE = &FFC2
jim 072.d3 Added int2irq.vec (wrksp+&20)
OS_SENDJOB = &FFC5
jim 072.d3 Added int3irq.vec (wrksp+&22)
OS_READJOB = &FFC8
jim 072.d3 Added int4irq.vec (wrksp+&24)
OS_DECODEJOB = &FFCB
jim 072.d3 Added c0irq.vec (wrksp+&26)
:
jim 072.d3 Added c1irq.vec (wrksp+&28)
FOR create=1 TO 2
jim 072.d3 Added t1irq.vec (wrksp+&2A)
:
jim 072.d3 Added t2irq.vec (wrksp+&2C)
FOR pass%=4 TO 7 STEP 3
jim 072.d3 Added txirq.vec (wrksp+&2E)
P%=&100*create : O%=data%
jim 072.d3 Added tyirq.vec (wrksp+&30)
[OPT pass%
jim 072.d3 Added s1rirq.vec (wrksp+&32)
:
jim 072.d3 Added s1rirq.vec (wrksp+&34)
.job_DemoJob
jim 072.d3 Added s2irq.vec (wrksp+&36)
CMP #1
jim 072.d3 Added adcirq.vec (wrksp+&38)
BEQ DemoJob_go
jim 072.d3 Renamed JobCall DownloadData38 to DownloadData740
LDA #1
jim 072.d3 Renamed JobCall UploadData38 to UploadData740
LDY #0
jim 072.d3 Renamed JobCall ExecuteCode38 to ExecuteCode740
RTS
jim 072.d3 Renamed JobCall ReadByte38 to ReadByte740
/
jim 072.d3 Renamed JobCall StoreByte38 to StoreByte740
.DemoJob_go
jim 072.d4 Hardware update
 
jim 072.d4 Re-assigned CallOS 37 to Write Power/Charge control
 
jim 072.d4 Re-assigned CallOS 38 to Read Power/Charge control
 
jim 072.d4 Moved int2irq.vec to wrksp+&22
 
jim 072.d4 Moved int3irq.vec to wrksp+&24
:
jim 072.d4 Moved int4irq.vec to wrksp+&26
.internal_handle
jim 072.d4 Moved c0irq.vec to wrksp+&28
CMP #1 ; Is it function call 1 (NameCode) ?
jim 072.d4 Moved c1irq.vec to wrksp+&2A
BNE internal_handle_2 ; No, check for function 2
jim 072.d4 Moved t1irq.vec to wrksp+&2C
/
jim 072.d4 Moved t2irq.vec to wrksp+&2E
LDA job_id ; Get our JOB ID number
jim 072.d4 Moved txirq.vec to wrksp+&30
STA zero_gp3 ; Place it in zero_gp3 for CALLOS
jim 072.d4 Moved tyirq.vec to wrksp+&32
LDX #job_names MOD 256 ; Our Job name table (LSB)
jim 072.d4 Moved s1rirq.vec to wrksp+&34
LDY #job_names DIV 256 ; Our Job name table (MSB)
jim 072.d4 Moved s1rirq.vec to wrksp+&36
LDA #2 ; CALLOS function 2
jim 072.d4 Moved s2irq.vec to wrksp+&38
JSR OS_CALLOS ; Call CALLOS
jim 072.d4 Moved adcirq.vec to wrksp+&3A
BCC internal_yes ; Found, go off and claim call
jim 072.d4 Added OS.LCDVDU (&FFB7) and lcdvdu.vec (wrksp+&20)
LDA #1 ; Not found, restore A
jim 072.d5 Re-assigned CallOS 37 to Write/Read Power/Charge control
JMP (ovec) ; Call back to old vector
jim 072.d5 Re-assgined CallOS 38 Read Battery Voltage
/
jim 072.d5 OS.CALLOS now re-entrant
.internal_handle_2
jim 072.d5 Fixed OS.PRINTER
CMP #2 ; Is it function call 2 (CodeName) ?
jim 072.d5 Started to add internal logging software
BNE internal_handle_3 ; No, unknown, pass on
jim 072.d5 Moved OS.LCDVDU to &FFB6
/
jim 072.d7 Added OS.PRINTERPOLL (&FFB3) and printerpoll.vec (wrksp+&3C)
LDA job_id ; Get our JOB ID
bob 073 Matched jim & bob sensor lookup tables
STA zero_gp3 ; Place it in zero_gp3 for CALLOS
bob 073 Added PatchMF
LDX #job_names MOD 256 ; Our Job name table (LSB)
jim 072.dj Added OS.CALLOS 51 Read keypad press
LDY #job_names DIV 256 ; Our Job name table (MSB)
jim 072.dj Added hard reset keypad press
LDA #3 ; CALLOS function 3
jim 072.dk Added JobCall ReadOutputs
JSR OS_CALLOS ; Call CALLOS
jim 072.do Changed soft reset of zero page locations
BCC internal_yes ; Found, go off and claim call
jim 072.do Changed MotorForward/Backward bitmaps
LDA #2 ; Not found, restore A
jim 072.dp Fixed OS.CALLOS WriteRTCbcd
JMP (ovec) ; Call back to old vector
jim 072.dq Fixed OS.CALLOS ReadRTCstring for correct 24hr operation
/
jim 072.dr Changed reset prompts
.internal_yes
bob 074 Added support for Little Bob
LDA #0 ; We want to claim call for some reason
jim 072.dy Added further RTC/CMOS support
/
jim 072.dB Removed double hard reset
.internal_handle_3
jim 072.dE Set _cpu.mode depending on external memory requirement
JMP (ovec) ; Call back to old vector
jim 072.dF Added Insight code
:
jim 073 Sub-release
.job_handle
jim 074 Modification to LCD code - uses bsy flag all the time now
PHA ; Make sure to preserve A
jim 074 Fixed OS.CALLOS 42 Write LCD Char Def
CPY call ; Check call wanted against base of ours
jim 074 Fixed OS.CALLOS 43 Read LCD Char Def
BCC job_handle_no ; Less than our base, so definately not ours
jim 075 Added simple battery charge code
TYA
jim 075 Fixed JobCall ReadSensorTable
SBC #no_of_calls ; Subtract number of calls we have
jim 075 Fixed OS.CALLOS 42 Write LCD Char Def
CMP call ; Now compare with base
jim 075 Fixed OS.CALLOS 43 Read LCD Char Def
BCC job_handle2 ; Yes, one of ours
jim 075 Added sleep code
/
jim 075 Added OS.CALLOS 52 Write Sleep Time
.job_handle.no
jim 075 Added OS.CALLOS 53 Read Sleep Time
PLA ; Restore A
jim 075 Modified battery charge code
JMP (ovec2) ; Call back to old vector
jim 076 Modified JobCall ReadSensorTable
/
jim 076 Modified OS.CALLOS 26 ReadKeypad to do debounce
.job_handle2
jim 077 Rewrote battery charging code
TYA
jim 077.2 Reduced stop charge threshold to 2
SEC
jim 077.3 Increased stop charge threshold to 3
SBC call ; Subtract our base from call
jim 077.3 Added battery voltage averaging
ASL A ; Times by 2 for offset into table
bill 077 Stripped
TAY ; Place in Y
bill 077 Moved ins to out port
LDA job_run_table,Y ; Get LSB of routine
bill 077 Added RTS
STA zero_gp1 ; Store it for indirect jump (LSB)
bill 077 Changed sensor ID system back to original, using p2.1
LDA job_run_table+1,Y ; Get MSB of routine
bill 077 Dedicated USB/RS selection line, RTS provisioned on p2.3
STA zero_gp1+1 ; Store it for indirect jump (MSB)
bill 077 Overload "bounce" timing
PLA ; Restore A
bill 078 CTS added
JMP (zero_gp1) ; Call our relevant routine
bill 079 Temperature fudge</nowiki>
:
.job_names ; List of our JobNames
EQUS "DemoJob":EQUB 13 ; Test job name
/
EQUB &FF ; &FF - marks end of table
:
.job_run_table ; List of routine addresses to match Jobs
EQUW job_DemoJob ; Test job routine
:
.call  : EQUB 0 ; Store for our JobCode base
.job_id : EQUB 0 ; Store for our JobId
.ovec  : EQUW 0 ; Store for old internal_vec value
.ovec2  : EQUW 0 ; Store for old unknownjob_vec value
:
EQUS STRING$(&100-(P% MOD 256),CHR$0)
.end_of_code ; end_of_code should be on page boundry
:
.execute
LDX #no_of_calls ; Number of JobCodes wanted
LDA #1 ; CALLOS, function 1, Claim JobCodes
JSR OS_CALLOS ; Call CALLOS
STX call ; Store base address of JobCodes allocated
STY job_id ; Store JOB ID given
CPX #0 ; Check we were given some codes
BEQ noinstall ; No, don't bother installing ourself
/
LDA internal_vec ; Get old internal_vec (LSB)
STA ovec ; Store it
LDA internal_vec+1 ; Get old internal_vec (MSB)
STA ovec+1 ; Store it
LDA #internal_handle MOD 256 ; Our internal handler (LSB)
STA internal_vec ; Place it in the vector (LSB)
LDA #internal_handle DIV 256 ; Our internal handler (MSB)
STA internal_vec+1 ; Place it in the vector (MSB)
/
LDA unknownjob_vec ; Get old unknownjob_vec (LSB)
STA ovec2 ; Store it
LDA unknownjob_vec+1 ; Get old unknownjob_vec (MSB)
STA ovec2+1 ; Store it
LDA #job_handle MOD 256 ; Our unknown job handler (LSB)
STA unknownjob_vec ; Place it in the vector (LSB)
LDA #job_handle DIV 256 ; Our unknown job handler (MSB)
STA unknownjob_vec+1 ; Place it in the vector (MSB)
/
LDX #end_of_code MOD 256 ; End of code (LSB)
LDY #end_of_code DIV 256 ; End of code (MSB)
STX jobin_buf ; Place it in jobin_buf (LSB)
STY jobin_buf+1 ; Place it in jobin_buf+1 (MSB)
LDA #65 ; JobCode for WriteLomem
JSR OS_DECODEJOB ; Call JobCall handler
/
.noinstall
RTS ; End - return to OS
:
.execcall
LDA #>(execute-address) ; Final execution address offset (LSB)
STA zero_gp1
LDA #<(execute-address) ; Final execution address offset (MSB)
STA zero_gp1+1
/
LDA #>(BitMap-address) ; BitMap address offset (LSB)
STA zero_gp2
LDA #<(BitMap-address) ; BitMap address offset (MSB)
STA zero_gp2+1
/
LDA #>(execcall-address) ; Length of data to relocate (LSB)
STA zero_gp3
LDA #<(execcall-address) ; Length of data to relocate (MSB)
STA zero_gp3+1
/
TXA ; Execcall REAL address
SEC
SBC #>(execcall-address) ; Subrtract length
TAX ; Put it back in X
TYA ; Ditto for MSB
SBC #<(execcall-address)
TAY
LDA #4 ; CALLOS function for Relocate
JMP OS_CALLOS ; Call CALLOS
/
.BitMap ; BitMap start
:
]
NEXT
:
OSCLI "SAVE code"+STR$create+" "+STR$~data%+" "+STR$~O%
:
NEXT</nowiki>

Revision as of 22:10, 3 November 2023

		Documentation for SmartBox OS 2.066

	The term JobCall is the concept of the routine itself, as opposed to
JobName (which is the actual name associated with it (eg.  NameCode)) and
JobCode (which is the actual number associated with it (eg.  3)).

	The term controller means the SmartBox, or any other incarnation
which may appear in future time !

	The term OS means the controllers' Operating System.


Preview

	There are a number of reasons why using Machine Code on the
controller is more desirable than using the various JobCalls via the serial
link.  One reason is speed, if the task which needs to be performed, has to
be performed very quickly (more quickly than can be achieved via the serial
link at least), then some code must be written to work in the controller
itself to achieve this sort of speed.  If the task required is to be a
"background" task (ie.  a task which may happen while other "things" carry
on as normal, like a change of state of a particular port) then it is far
easier to let the controller do all the work, maybe under interupts, than
continuously poll the controller checking the state of various things. 
Another reason may be that you would like to add another JobCall to the OS,
extending the calls available to external applications (and the user at the
same time), without the user having to write the code himself to achieve the
aim.  For advanced users, there is also the ablity to alter the way the OS
does various things and to have the controller in their "power", for
dedicated tasks etc.

	One advantage of writing IN the controller is that that "function"
is available to any computer using the serial link, and is thus not fixed to
one computer type or even a computer at all, where the controller is used
free running, doing a particular task which doesn't need the intervention of
the user's own computer.


Call types

	There are three different types of machine code routines which can
be added to the controller.  Each one is suited to a more advanced level
than the other, which also relates to the amount of work needed to actually
get the routine working !  The three levels are such:

	a) Simple stand alone routine.  Here the routine is just the routine
itself with no supporting code (ie.  code to integrate with the rest of the
OS).  It has to be called directly, by use of the ExecuteCode JobCall, thus
it usually resides at a fixed address and obviously does not offer the user
friendly way of accessing itself like there is with normal OS calls.

	b) Extended JobCall.  There is one JobCall in the OS (ExtendJob)
which can call various routines residing in memory, each one is identified
by a 8 bit number.  The disadvantages of this way is that the call hasn't
got it's own JobName/JobCode and needs the extra byte to identify itself. 
This is ideal for the quick routine which the user would like to add for
himself without having to resort to the extra code needed for the full
JobCode implementation.

	c) Full JobCall.  This is where the routine can define itself one
(or a number) of JobNames/JobCodes, it can integrate itself into an
extension of the OS in a complete way.  The disadvantage is that extra
coding is needed to deal with the various OS calls it has to service and
that extra work is needed to produce a suitable file to place in the
controller.

	Normally with most processors, including the 65c02, the machine code
produced is produced for a fixed memory address, and it cannot be made to
run in a different place in memory unless extra work is carried out by the
user, or at least a combination of the user and the OS.  For the controller
this is a very severe disadvantage as most extensions are downloaded as
"modules" and to optimise memory a system has to be devised to enable
routines to be placed anywhere in memory, thus allowing no memory to be
wasted.  In the OS there is a routine to do just this, though it does need
some help.  A special version of the users code has to be developed, or
rather it has to be "processed" to add extra information on the end.  Extra
code is needed to set up a few pointers and call the OS routine to relocate. 
See later.


Memory

	The first 256 bytes of memory are somewhat special, most zero page
locations are reserved for use by the OS:

	0	General workspace, used to pass parameters to some OS
			routines, can also be used by other routines DURING
	14		their execution.
	15	Reserved
	6f
	70	Available to the user
	9f
	a0	Accumulator store for irqs (irq_A)
	a1	Two byte counter, decremented at 100hz, useful for temporary
	a2		timing purposes (fcount)
	a3	RAM size, high byte of RAM size of machine, ie. for 32k
			this will be &80 (RAM_size)
	a4	Reserved
	af
	b0	Used by the OS
	ff

	The rest of memory space is used for the processors stack, OS'
buffers, tables, vectors and, of course, the user available memory.

	A breakdown of the rest of memory:

	&100 to &1FF is the 65c02's stack.

	&200-&2FF is used by the OS for various system functions and also
contains the vector table, more on that later.

	&300-&3FF is the serial input buffer.

	&400-&47F is the internal job code call output buffer. (jobout_buf)

	&480-&4FF is the internal job code call input buffer. (jobin_buf)

	&500-&5FF is the job status table.

	&600 to the end of RAM memory is for the user. The user should not
		assume what the top of AVAILABLE memory is and use the
		system variable HIMEM.

	In the controller the maximum limit of RAM is &8000 (which is 32k),
from &8000-&DFFF lies the memory mapped hardware, from &E000 to &FFF9 lies
the OS and from &FFFA to &FFFF lies the 65c02 vectors.

	So the user has his own limits of free memory.  The lowest limit is
called LOMEM and the highest limit is called HIMEM, everything between these
boundaries is termed "available" for use by anyone, the user or the OS. 
Anyone can also claim memory, thus making private to them and not allowing
anyone else to use it.  You do this by altering the value of LOMEM, HIMEM
should not be moved.  Of course anyone claiming memory should first check
that there is enough memory available for the amount they want to claim for. 
To take a common example, a downloadable routine which installs itself as a
extension routine, The download routine will first of all read LOMEM, read
HIMEM, check there is enough memory for the routine, download it to LOMEM
and call it, the downloaded routine will then relocate itself and alter
LOMEM so it is protected.

	There is one other memory limit, this is called TOPMEM, this is the
absolute top limit of RAM, HIMEM will normally equal TOPMEM, but a future OS
may alter HIMEM to below TOPMEM.  TOPMEM should be ignored ideally, and
HIMEM used.


OS Calls

	 The OS has calls (ie.  a jump instruction which you call, which
then calls, via a RAM vector, a routine in the OS) setup near the top of
memory, through these calls you do everything associated with the
controller.  The calls are are such:

 OS_PRINTER	at address &FFB9
 OS_CALLOS	at address &FFBC
 OS_SENDBYTE	at address &FFBF
 OS_READBYTE	at address &FFC2
 OS_SENDJOB	at address &FFC5
 OS_READJOB	at address &FFC8
 OS_DECODEJOB	at address &FFCB

	The address is the one to call for the particular routine.

	OS_PRINTER is reserved for future use.
	OS_CALLOS calls upon the OS to do various things internally.
	OS_SENDBYTE sends a byte out of the serial port.
	OS_READBYTE reads a byte from the serial port.
	OS_SENDJOB 'sends' a value to the JobCall caller.
	OS_READJOB 'reads' a value from the JobCalls caller.
	OS_DECODEJOB decodes the Job number held in A and acts on it.

	Note none of these things should be used in interupt routines,
except OS_CALLOS, of any sort, except if YOU know what state the machine is
in and WHY.


Serial Port

	The serial port on the controller is currently based on the 6850
UART, this need not bother you as the OS deals with the thing entirely by
itself.  The chip is setup by the OS to always have a word format of 8n1 and
a baud rate of 9600, this translates to 960cps.  The 6850 has two divide
rates for the baud rate, the OS only uses one, using the other one is beyond
the scope of this document and shouldn't be neccessary to use.

	Only receive interupts are used, transmission of characters is done
on a polled basis.

	As said, there are two calls OS_READBYTE and OS_SENDBYTE. 
OS_READBYTE tests the serial input buffer and returns the character (if any
in the A register), the c flag states whether there was a character
returned.

 Call	: OS_READBYTE
 Entry	:	A, X, Y, c, z = undefined
 Exit	:	X, Y preserved
		z = undefined
		if c = 1 (no character received)
			A is preserved
		if c = 0 (character received)
			A = character received

	OS_SENDBYTE sends the character in the A register out of the serial
port, taking into consideration flow control etc.

 Call	: OS_SENDBYTE
 Entry	:	A = character to send
		X, Y, c, z = undefined
 Exit	:	A, X, Y, c is preserved
		z = undefined


OS_READJOB and OS_SENDJOB

	To send/receive data from the user (either internally or via the
serial port) you call OS_SENDJOB/OS_READJOB for the next byte.  If your
JobCall requires dynamic use of data, ie.  interaction, then a check of the
enviroment should be done to check the call came from the serial port.  One
example of this is the OS JobCall MultipleServer, which requires dynamic use
of sending a stream of data until a reaction from the user to tell it to
stop.

 Call	: OS_READJOB
 Purpose: To read job value from the user
 Entry	:	A, X, Y, c, z = undefined
 Exit	:	A = byte read
		X, Y is preserved
		c, z = undefined
 Note   : When coming from the serial port it waits for a character to be
 received, if you are doing a serial only call and wish to have a loop
 checking for a character from the user while doing "your own thing", then
 OS_READBYTE should be used.  For internal calls this reads data out of
 jobin_buf.

 Call	: OS_SENDJOB
 Purpose: To give a job value back to the user
 Entry	:	A = byte to give
		X, Y, c, z = undefined
 Exit	:	A, X, Y is preserved
		c, z = undefined
  Note   : This will send either out to the serial port or place a byte in
 jobout_buf.  For internal calls this stores the value in jobout_buf, which
 is allocated 128 bytes, no bound checking is done so sending more than 128
 bytes to OS_SENDJOB will cause corruption of some memory.


OS_DECODEJOB

	 JobCalls in practise are really only used by the user, JobCalls
tend not to call other calls at all, infact you shouldn't really call
another call unless you were called from the serial port.  If you do need to
call another JobCall then you do it like this; with each call you have
associated with it a number, and the call might require some data or give
some data back or both.  With the serial port this is simple, you just send
data down and receive data as it is produced.  You can't do this with
internal calls, and some calls will not let you call them from inside the
controller, you HAVE to call them from outside.  But for the ones which do
let you, you do it with two data blocks, one for input TO the call and one
for output FROM the call.  These are setup in a position in memory
(jobin_buf and jobout_buf), each one has a maximum of 128 bytes and this
should not be overflowed (OS_READJOB and OS_SENDJOB do NOT do any bound
checking).  So to start with you place in the input block the data you want
to give the call (if any), call it, and then act on the data in the output
block (if any).  The OS call OS_DECODEJOB is the one which actually calls
the JobCall handler and eventually the actual routine, you tell the JobCall
handler which call you want by placing the JobCode in the A register.

So for OS_DECODEJOB:

 Call	: OS_DECODEJOB
 Entry	:	A = JobCode
		X, Y, c, z = undefined
		jobin_buf should contain any input data
 Exit	:	A = flag
		X = number of bytes given back by routine
		Y = undefined
		c = status, if set JobCall does not exist
		z = status, if set JobCall cannot be called internally
		jobout_buf contains any data sent from routine

	A typical example would be:

	LDA #readhimem		; JobCode for ReadHimem
	JSR OS_DECODEJOB	; Call the JobCall handler
	LDX jobout_buf		; Read HIMEM from buffer LSB
	LDY jobout_buf+1	; Read HIMEM from buffer MSB
	STX jobin_buf		; Place LSB in input buffer
	STY jobin_buf+1		; Place MSB in input buffer
	LDA #writelomem		; JobCode for WriteLomem
	JSR OS_DECODEJOB	; Call the JobCall handler

which would read HIMEM, and then write LOMEM to be the same value, leaving
no spare memory left.

	The reason why you should not call an internal call from another
internal call is that the same buffers (jobin_buf and jobout_buf) would be
used for both calls, causing conflict between the two calls, or rather to
the originator of the primary call !  The only time it would be safe if is
if you have already got all the data you want from jobin_buf and haven't
placed any in the jobout_buf (it would be lost if you had done).


CallOS

	This OS call does various calls which are completely linked to the
OS, everything from relocating code to reading and writing the hardware
ports.

	As normal, a "function" request value is placed in the A register,
any extra data is put in the X and Y registers and a call to OS_CALLOS is
made, and data is passed back in X, Y, c and z.  The A register will return
0 for all routines except for calls 0 (deemed always to exist anyway), 12 (A
is reserved for future use), 14 (returns 1 instead) and 15 (12, 14 and 15 do
not return 12, 14, 15 in A though), if the routine passes back the same
value in A (the exception being call 0) as you gave it then that OS_CALLOS
function is not supported.

 CALLOS	: 0
 Purpose: Returns the OS version number
 Entry	:	A = 0
		X, Y, c, z = undefined
 Exit	:	A = Hardware release version
		XY = OS version number
		c, z is preserved
 Note	: Hardware version 0 is obsolete
	  Hardware version 1 is SmartBox
	  	VIA at &8030
	  	UART at &8010
	  	ADC at &8000
	  	AUX_PORT (inputs) at &8020 

 CALLOS	: 1
 Purpose: To claim a block of JobCodes
 Entry	:	A = 1
		X = number of JobCodes required
		Y, c, z = undefined
 Exit	:	A = 0
		X = base JobCode, if 0, no big enough block available
		Y = Job ID number, 0 if no big enough block
		c = set if no big enough block
		z is preserved
 Note	: This call claims JobCodes codes from the JobCode list.  JobCodes
are in the range 0 to 255, 0 is special and is the JobCall "Blank".  All the
OS ones are spaced out but an "application" can claim a block of JobCodes,
usually just the 1 call.  If there is not a contigous block big enough then
no JobCodes are allocated and the appropiate result is returned.  The Job ID
is from 0 to 255, 0 is unallocated, 1 is the OS calls, the rest are external
applications, each successful call increments the Job ID returned, meaning a
maximum of 255 successful calls (not important as their is not that many
free calls available !), the Job ID can be used to check whether a JobCall
request is yours.

 CALLOS	: 2
 Purpose: To handle NameCode request for the application.
 Entry	:	A = 2
		XY = block pointing to block of JobNames
		c, z = undefined
		jobin_buf contains JobName (terminated by CR)
		zero_gp3 = JobId (as returned by CALLOS 1)
 Exit	:	A = 0
		XY = undefined
		z is preserved
		if c = 1 (Name not found)
		if c = 0 (Name found)
			jobin_buf contains the JobCode
 Note	: Used via the internal_vec to convert from JobName to job code, the
table format is just a list of the JobNames (each one CR terminated)
terminated by a &ff

 CALLOS	: 3
 Purpose: To handle CodeName request for the application.
 Entry	:	A = 3
		XY = block pointing to block of JobNames
		c, z = undefined
		jobin_buf contains JobCode
		zero_gp3 = job id (as returned by CALLOS 1)
 Exit	:	A = 0
		XY = undefined
		z is preserved
		if c = 1 (JobCode not found)
		if c = 0 (JobCode found)
			jobin contains JobName (terminated by CR)
 Note	: See above

 CALLOS	: 4
 Purpose: To relocate code
 Entry	:	A = 4
		XY = execution address (offset from start of code)
		c, z = undefined
		zero_gp1 = offset address of execution after relocation
		zero_gp2 = offset address of relocation BitMap
		zero_gp3 = length of code to relocate
 Exit	:	No exit, calls begining of code straight away
 Note	: See later section on Relocation

 CALLOS	: 5
 Purpose: To "describe" the enviroment
 Entry	:	A = 5
		X, Y, c, z = undefined
 Exit	:	A = 0
		c, z is preserved
		XY = address of table, format as such:
		1st byte: number of bytes (ie. 14 currently)
		byte 2+:
			address of VIA
			address of ACIA
			address of ADC
			address of AUX_PORT
			address of jobs_status
			address of jobin_buf
			address of jobout_buf
 Note	: It is preferred that this call is consulted first if direct use of
the hardware is going to be used to get the correct addresses, or use CALLOS
0 and decide what addresses/configuration to use from the Hardware version
number.

 CALLOS	: 6
 Purpose: To read a register in the VIA
 Entry	:	A = 6
		X = register (0 to 15)
		Y, c, z = undefined
 Exit	:	A = 0
		Y = value read
		X, c, z is preserved
 Note	: The register number is not checked for range.  This call allows
access to the hardware without actually directly accessing the hardware
yourself.

 CALLOS	: 7
 Purpose: To write to a register in the VIA
 Entry	:	A = 7
		X = register (0 to 15)
		Y = value to write
		c, z = undefined
 Exit	:	A = 0
		X, Y, c, z is preserved
 Note	: See above

 CALLOS	: 8
 Purpose: To read a register in the ACIA
 Entry	:	A = 8
		X = register (0 to 15)
		Y, c, z = undefined
 Exit	:	A = 0
		Y = value read
		X, c, z is preserved
 Note	: See above

 CALLOS	: 9
 Purpose: To write to a register in the ACIA
 Entry	:	A = 9
		X = register (0 to 15)
		Y = value to write
		c, z = undefined
 Exit	:	A = 0
		X, Y, c, z is preserved
 Note	: See above

 CALLOS	: 10
 Purpose: To read a register in the ADC
 Entry	:	A = 10
		X = register (0 to 15)
		Y, c, z = undefined
 Exit	:	A = 0
		Y = value read
		X, c, z is preserved
 Note	: See above

 CALLOS	: 11
 Purpose: To write to a register in the ADC
 Entry	:	A = 11
		X = register (0 to 15)
		Y = value to write
		c, z = undefined
 Exit	:	A = 0
		X, Y, c, z is preserved
 Note	: See above

 CALLOS	: 12
 Purpose: To read a ADC channel
 Entry	:	A = 12
		X = channel number to read (0 to 3)
		Y, c, z = undefined
 Exit	:	A = reserved for future use
		if c = 0 (8 bit reading)
			Y = reading
			X = undefined
		if c = 1 (16 bit reading)
			X = reading (LSB)
			Y = reading (MSB)
		z = undefined
 Note	: The A register MAY not return 0, it is guaranteed though that it
will not return 12.

 CALLOS	: 13
 Purpose: To read a register in the AUX_PORT
 Entry	:	A = 13
		X = register (0 to 15)
		Y, c, z = undefined
 Exit	:	A = 0
		Y = value read
		X, c, z is preserved
 Note	: See above

 CALLOS	: 14 (OS 2.066+)
 Purpose: To start sensor type checking
 Entry	:	A = 14
		X, Y, c, z = undefined
 Exit	:	A = 1
		Y = value read
		X, c, z is preserved
 Note	: A returns 1.  This flags the ADC routines to start checking the
sensors.  After calling this routine, you should poll CALLOS 15 to check
when all the sensors have been checked.

 CALLOS	: 15 (OS 2.066+)
 Purpose: To check the status of sensor checking
 Entry	:	A = 15
		X, Y, c, z = undefined
 Exit	:	A = status
		XY = pointer to sensor types block
		c, z is preserved
 Note	: A does not return 0, though it is guaranteed to not return 15.
	  Status of 0 = checking finished and no futher check pending
	  Status of 1 = Waiting for current ADC channel to finish converting
				before switching sensor checking on
	  Status of >127 = Checking sensor types
	  The block pointed to by XY consists of 4 bytes, each byte
	  	contains the sensor type for the appropiate sensor, a type
	  	of 0 means no sensor present

 CALLOS	: 16 (OS 2.069+)
 Purpose: To set the OS' irq mask for the VIA
 Entry	:	A = 16
		X = EOR mask
		Y = AND mask
		c, z = undefined
 Exit	:	A = 0
 		X = old mask value
		Y = new mask value
		c, z is preserved
 Note	:

 CALLOS	: 17 (OS 2.069+)
 Purpose: To set the OS' irq mask for the ACIA
 Entry	:	A = 17
		X = EOR mask
		Y = AND mask
		c, z = undefined
 Exit	:	A = 0
 		X = old mask value
		Y = new mask value
		c, z is preserved
 Note	:

 CALLOS	: 18 (OS 2.069+)
 Purpose: To set the OS' irq mask for the ADC
 Entry	:	A = 18
		X = EOR mask
		Y = AND mask
		c, z = undefined
 Exit	:	A = 0
 		X = old mask value
		Y = new mask value
		c, z is preserved
 Note	:

 CALLOS	: 19 (OS 2.069+)
 Purpose: To set the ACIA's ctrl register and OS' soft copy
 Entry	:	A = 19
		X = EOR mask
		Y = AND mask
		c, z = undefined
 Exit	:	A = 0
 		X = old mask value
		Y = new mask value
		c, z is preserved
 Note	:

 CALLOS	: 20 (OS 2.069+)
 Purpose: To set the reset vector for battery back RAM support
 Entry	:	A = 20
                XY = address to set reset vector or 0 for read only
		c, z = undefined
 Exit	:	A = 0
 		XY = old reset vector address
		c, z is preserved
 Note	: Sets the reset vector to address supplied and sets check bytes in
workspace.


Vectors

	Some parts of the system are controlled by vectors, vectors also
exist to patch the normal operation of some parts of the system.  Vectors
are RAM pointers, which are called to point to the routine to use.  Being in
RAM this means that they can be altered by the user to allow the user to
modify the operation of part, or all, of a system call.  All the call
routines (eg.  OS_DECODEJOB, OS_READBYTE etc.) are called via a RAM vector. 
There are vectors which the user patches to pick up unknown jobcodes, and to
service requests like NameCode/CodeName etc.

	The vectors are placed at address &200 in memory, each are 2 bytes
long.

 brk_vec	at address &200
 nmi_vec	at address &202
 irq_vec	at address &204
 irq2_vec	at address &206
 sendserial_vec	at address &208
 readserial_vec	at address &20A
 sendjob_vec	at address &20C
 readjob_vec	at address &20E
 decode_job_vec	at address &210
 unknownjob_vec	at address &212
 extjob_vec	at address &214
 centisec_vec	at address &216
 internal_vec	at address &218
 callos_vec	at address &21A
 printer_vec	at address &21C
 reset_vec	at address &21E

	The brk_vec is called whenever there is a BRK error, which should
not occur under normal use, as the controller OS does not make use of BRK
errors.
	The nmi_vec is called whenever there is a NMI request (not really
applicable, as the NMI line is not connected to anything).
	The irq_vec is called whenever there is an interupt.
	The irq2_vec is called whenever an unknown interupt is encountered,
ie.  didnXt come from the controllerXs VIA, ACIA or ADC, or the interupt
from the VIA was not used by the OS.
	The sendserial_vec is called when OS_SENDBYTE is called.
	The readserial_vec is called when OS_READBYTE is called.
	The sendjob_vec is called when OS_SENDJOB is called.
	The readjob_vec is called when OS_READJOB is called.
	The decodejob_vec is called when OS_DECODEJOB is called.  Note that
OS_DECODEJOB first calls a OS routine which sets a flag to say it's been
called internally, JobCode requests the OS gets from the serial port are
called via this vector, with a flag saying "from the serial port".
	The unknownjob_vec is called whenever an unknown JobCode is
encountered.
	The extendedjob_vec is called whenever the ExtendedJob is called,
the A register holds the extension value.
	The centisecond_vec is called 100 times a second from the IRQ
routine from interupts off timer 1 of the VIA.  The OS has its pulsing
routines on the end of this.  Note that you should return with a RTS, not
RTI, you may also corrupt any of the registers.
	The internal_vec is called when some information is needed from
various parts of the system, which includes the OS whch lies on the end of
this vector.  An example is NameCode, which calls this vector to ask
everybody if they recognise the JobName in question.
	The callos_vec is called when OS_CALLOS is called.
	The printer_vec is called when OS_PRINTER is called.
	The reset_vec is called when the OS gets a reset and the internal
check bytes flag the integrity of the RAM.  It is first called with C
cleared for everything to setup vectors and then called with C set for a
foreground "language" application to start up.  Use CALLOS 20 to set this
vector.
	All vectors should be 'daisy chained', ie.  any routine which uses
any of them should store the current value in its own workspace and alter
the vector and at the end of the routine which services the vector should
jump to the original saved value, if this isnXt done the system may 'fall'
down.
	unknown, extendjob and irq2 all point to (in the present OS) to a
routine which just returns.  brk in the current OS just resets the stack and
jumps back to the OS' main idle loop.
	Irqs are reenabled before brk_vec is called


Irq_vec

	This is called when the 65c02 receives an irq from a device.  As the
65c02 signals brk and irq through the same cpu vector the OS first finds out
which one it really was and calls either brk_vec or irq_vec.  It also stores
the A reg at address &a0, as the A reg is corrupted while it finds out which
vector to really call, any irq routine which has claimed the irq should
restore the A reg from &a0 before returning via a RTI, else you should pass
on the call to the old vector owner.

	irq2_vec is called by the OS irq routine if it does not find a irq
it can service (or as been masked out) in as-good-as the same conditions as
irq_vec is called.  The OS has a default irq2_vec handler of restoring A
from &a0 and doing a RTI.


Internal_vec and Unknownjob_vec

	The main operation of extended JobCalls rely on patching some
vectors, two infact.  These two are unknownjob_vec and internal_vec. 
unknownjob_vec is the one which the OS calls to actually tell everybody that
a JobCall has been requested which it does not know about (the JobCall MUST
have been claimed before hand with CALLOS, function 1, or else it is not
passed on).  internal_vec is the one which the OS calls in request to a
NameCode or CodeName.

	The vectors are defined as thus:

 internal_vec

 Function: 0
 Purpose:	Nothing
 Entry	:	A = 0
		X, Y, c, z = undefined
 Exit	:	Leave everything as it is and exit straight away
 Note	: Used for claimed calls

 Function: 1
 Purpose:	NameCode request
 Entry	:	A = 1
		X, Y, c, z = undefined
		jobin_buf contains JobName (CR terminated)
 Exit	:	 if JobName recognised then
			A = 0
			first byte of jobin_buf contains matched JobCode
		if JobName not recognised then
			A = 1
		X, Y, c, z = undefined
 Note	:

 Function: 2
 Purpose:	CodeName request
 Entry	:	A = 2
		X, Y, c, z = undefined
		jobin_buf contains JobCode
 Exit	:	if JobCode recognised then
			A = 0
			jobin_buf contains matched JobName (CR terminated)
		if Code not recognised then
			A = 2
		X, Y, c, z = undefined
 Note	:

	All other calls (ie.  not 0, 1 or 2 should be passed on straight
away for future expansion)

 unknownjob_vec

 Purpose:	To pass on unknown JobCalls to the correct owner
 Entry	:	A = enviroment (0 = internal call, 1 = serial call)
		Y = JobCode
		X = JOB ID of owner
		c, z = undefined
 Exit	:	if jobcode yours then
		if wrong enviroment then
			A = 1
			Y = 0
			X, c, z = undefined
		if right enviroment then
			perform function
			A = 0
			Y = 0
			X, c, z = undefined
		if jobcode not yours then
			pass on with registers unaltered
 Note	:


Starting Up

	The setup procedure for setting up extension job calls is to:

	a) Claim the number of JobCodes you want
		if success:
	b) patch the internal_vec and unknownjob_vec vectors
	c) move LOMEM up to protect your program
	d) do anything else for initial startup you may want to do
	e) return back, ready and waiting

  This in code looks like:

execute
	LDX #no_of_calls	; Number of JobCodes wanted
	LDA #1			; CALLOS, function 1, Claim JobCodes
	JSR OS_CALLOS		; Call CALLOS
	CPX #0			; Check we were given some codes
	BEQ noinstall		; No, don�t bother installing ourself
	STX call		; Store base address of JobCodes allocated
	STY job_id		; Store JOB ID given

	LDA internal_vec	; Get old internal_vec (LSB)
	STA ovec		; Store it
	LDA internal_vec+1	; Get old internal_vec (MSB)
	STA ovec+1		; Store it
	LDA #>internal_handle	; Our internal handler (LSB)
	STA internal_vec 	; Place it in the vector (LSB)
	LDA #<internal_handle	; Our internal handler (MSB)
	STA internal_vec+1	; Place it in the vector (MSB)

	LDA unknownjob_vec	; Get old unknownjob_vec (LSB)
	STA ovec2 		; Store it
	LDA unknownjob_vec+1	; Get old unknownjob_vec (MSB)
	STA ovec2+1		; Store it
	LDA #>job_handle	; Our unknown job handler (LSB)
	STA unknownjob_vec	; Place it in the vector (LSB)
	LDA #<job_handle	; Our unknown job handler (MSB)
	STA unknownjob_vec+1	; Place it in the vector (MSB)

	LDX #>WriteLomem	; Point to JobName
	LDY #<WriteLomem
	JSR findjob		; Find WriteLomem code
	BEQ noinstall		; Doesn't exist !
	LDX #>end_of_code	; End of code (LSB)
	LDY #<end_of_code	; End of code (MSB)
	STX jobin_buf		; Place it in jobin_buf (LSB)
	STY jobin_buf+1		; Place it in jobin_buf+1 (MSB)
	JSR OS_DECODEJOB	; Call JobCall handler

noinstall
	RTS			; End - return to OS

findjob
	STX zero.gp1		; Zero page
	STY zero.gp1+
	LDY #0
findthisjoblo
	LDA (zero.gp1),Y
	STA jobin.buf,Y		; Store name in jobin_buf
	INY
	CMP #13
	BNE findthisjoblo
	LDA #3			; NameCode
	JSR OS.DECODEJOB	; Call NameCode
	LDA jobout.buf		; Get returned value
	RTS

WriteLomem	STR "WriteLomem"

	This will setup the various vectors and memory pointers for the
applicaion, the actual routines (internal_handle and job_handle) are fairly
straight forward and simple:

internal_handle
	CMP #1			: Is it function call 1 (NameCode) ?
	BNE internal_handle_2	; No, check for function 2
	LDA job_id		; Get our JOB ID number
	STA zero_gp3		; Place it in zero_gp3 for CALLOS
	LDX #>job_names		; Our Job name table (LSB)
	LDY #<job_names		; Our Job name table (MSB)
	LDA #2			; CALLOS function 2
	JSR OS_CALLOS		; Call CALLOS
	BCC internal_yes	: Found, go off and claim call
	LDA #1			; Not found, restore A
	JMP (ovec)		; Call back to old vector

internal_handle_2
	CMP #2			; Is it function call 2 (CodeName) ?
	BNE internal_handle_3	; No, unknown, pass on
	LDA job_id		; Get our JOB ID
	STA zero_gp3		; Place it in zero_gp3 for CALLOS
	LDX #>job_names		; Our Job name table (LSB)
	LDY #<job_names		; Our Job name table (MSB)
	LDA #3			; CALLOS function 3
	JSR OS_CALLOS		; Call CALLOS
	BCC internal_yes	; Found, go off and claim call
	LDA #2			; Not found, restore A
	JMP (ovec)		; Call back to old vector

internal_yes
	LDA #0			; We want to claim call for some reason

internal_handle_3
	JMP (ovec)		; Call back to old vector

job_handle
	PHA			; Mark sure to preserve A
	CPY call		; Check call wanted against base of ours
	BCC job_handle_no	; Less than our base, so definately not ours
	TYA
	SBC #no_of_calls	; Subtract number of calls we have
	CMP call		; Now compare with base
	BCC job_handle2		; Yes, one of ours

job_handle.no
	PLA			; Restore A
	JMP (ovec2)		; Call back to old vector

job_handle2
	TYA
	SEC
	SBC call		; Subtract our base from call
	ASL A			; Times by 2 for offset into table
	TAY			; Place in Y
	LDA job_run_table,Y	; Get LSB of routine
	STA zero_gp1		; Store it for indirect jump (LSB)
	LDA job_run_table+1,Y	; Get MSB of routine
	STA zero_gp1+1		; Store it for indirect jump (MSB)
	PLA			; Restore A
	JMP (zero_gp1)		; Call our relevant routine

job_names			; List of our JobNames
	STR "TestJob"		; Test job name
	DFB &FF			; &FF - marks end of table

job_run_table			; List of routine addresses to match Jobs
	DFW job_TestJob		; Test job routine


Relocation

	The relocation method used will not do a complete relocation, ie. 
from ANY address to ANY address.  The only constraint on the addresses is
that they must be the same offset in the page, which means, &2602 and &3402
is alright but &2602 and &3455 is not, ie.  the difference between the two
addresses must be a multiple of 256 bytes, as only the MSB is altered, this
should not cause any real problems.  What is meant by the two addresses is
the address the code was originally assembled at and the address it wants
relocating to.  Because of this programs must be multiples of 256 bytes, ie. 
the low byte of LOMEM must ALWAYS be 0, your program should be padded out.

	The relocation mechanism works by having a table, and to each byte
of code there is one BIT, if unset it means the byte of code should not be
relocated, else it should be, thus the BitMap table is 8 times smaller than
the code.  To generate the BitMap you have to assemble the code at two
different places, and compare both sets of assembled code against each
other, the differences are where the code is to be relocated.  The code
which is sent up to the controller is assumed to be assembled at address
&100, so the first code should be assembled at &100, the other one should be
assembled at another address, say &600, and the first one be sent up with
the BitMap.  A program is availble for the Elk/Beeb/Arc with 65Tube/Mac with
BBC BASIC to compare the two files and create the BitMap.

	All the program has to do to relocate itself is execute this bit of
code first of all: (address is the base address of the program, ie.  &100
for the downloaded version)

execcall
	LDA #>(execute-address)	; Final execution address offset (LSB)
	STA zero_gp1
	LDA #<(execute-address)	; Final execution address offset (MSB)
	STA zero_gp1+1

	LDA #>(BitMap-address)	; BitMap address offset (LSB)
	STA zero_gp2
	LDA #<(BitMap-address)	; BitMap address offset (MSB)
	STA zero_gp2+1

	LDA #>(execcall-address); Length of data to relocate (LSB)
	STA zero_gp3
	LDA #<(execcall-address); Length of data to relocate (MSB)
	STA zero_gp3+1

	TXA			; Execcall REAL address
	SEC
	SBC #>(execcall-address); Subrtract length
	TAX			; Put it back in X
	TYA			; Ditto for MSB
	SBC #<(execcall-address)
	TAY
	LDA #4			; CALLOS function for Relocate
	JMP OS_CALLOS		; Call CALLOS

BitMap				; BitMap start

	This will relocate the code and automatically call the execution
address, which should be the initialisation code (ie.  setup internal_vec
and unknownjob_vec etc.).  "address" is the address the code is started to
assemble at, &100 for the version to be uploaded.  The upload routine should
pass in X and Y (via ExecuteCode) the address of the start of execcall, this
means that the routine can tell where it is in memory, subtracting the
length gives the start of the code to relocate.


Layout Summary

	The layout of a full application should be like this by now:

	start of code
		program job codes, data etc.
		internal handler
		job handler
	end of code
		initialisation (setup internal etc.)
	end of code to be relocated
		execution (relocation)
		relocation BitMap

	Because of memory space the initialisation code should be placed
outside the reserved memory limit of the program, as it is only called once
and not needed again.


 Other miscellaneous calls:

Call		: OS_PRINTER
Purpose	: To place a character in the printer buffer
Entry		: A = character to send
		  X, Y, c, z = undefined
Exit		: A, X, Y is preserved
		  if c = 0 (printed)
			The printer is on and the character was inserted
		  if c = 1 (not printed)
			The printer is off and the character was forgotten
		  z = undefined
Note		: The character goes into the printer buffer and will be
actually sent to the printer when the printer asks for it. If the printer
was not awake to begin with, an attempt is made to wake it up, thus
meaning that in some circumstances when the printer wasn't ready a
character may be lost, this cannot be helped. If the printer is off (via
the BCD switch) then the character will be forgotten as the Printer Buffer
is inactive.


  The module is located at &8000 in memory, and should be a EPROM placed
in the second ROM socket in the controller (ie. the top RAM socket). This
module is primary designed to turn the Controller into a dedicated task,
making it perform some function from switch on. It could also be used to
add more permant JobCalls to the Controller, in the process taking up much
less RAM), but because of there being only one Module present this feature
is limited, and is much more suited to a collection of the user's own
personal routines.
  For the OS to recognise a module as being in place the text "Module" is
looked for, the layout of a module should be like this (starting at
&8000):

Offset		value			comment
 0		"Module"		The OS checks for this string
 6		0			End of check string
 7		Language entry		Address of 'Language' entry
 9		Service entry		Address of 'Service' entry
11		"<Module Title>" 	Module title
		0			End of Module title

		"<Module part>"		Part Module Title
		0			End of Part Module Title
		"x.xx"			Part version number

		&FF			End of parts list

  The language entry is a 2 byte value holding the address of the language
entry routine, this is called on reset, if the BCD is set correctly. A
value of less than &8000 means there is no valid language entry and any
language calls are ignored. If a RTS is made the OS carries on as it would
have if there wasn't a language call.

  The service entry is a 2 byte value holding the address of the service
entry routine, this is called at various appropiate moments with service
numbers in A. A list of current calls is thus:

	  0	- Not used
	  1	- Unknown Job Code
	  2	- Centisecond call
	  3	- Irq2 (unknown IRQ)
	  4	- internal_vec call (exit with A=0 to claim)
	254	- RESET
	255	- BRK

  On exit all registers and flags should be preserved. For call 1 the
unknown job value is held in the Y register, the enviroment is held in the
X register, on exit, X should contain the flag, as A does from the exit of
unknownjob_vec. For the rest of calls, X and Y are undefined.

  For both Service and Language entry points, a value of 0 means "there is
no valid routine" and the module isn't called for that type of entry.



  Finally a full implementation of a JobCall, from start to finish.

 DIM data% &1000
 :
 no_of_calls = 1
 :
 VIA = &E030
 ACIA = &E010
 ADC = &E000
 AUX_PORT = &E020
 brk_vec = &200
 nmi_vec = &202
 irq_vec = &204
 irq2_vec = &206
 sendserial_vec = &208
 readserial_vec = &20A
 sendjob_vec = &20C
 readjob_vec = &20E
 decodejob_vec = &210
 unknownjob_vec = &212
 extjob_vec = &214
 centisec_vec = &216
 internal_vec = &218
 callos_vec = &21A
 printer_vec = &21C
 zero_gp1 = 0
 zero_gp2 = 2
 zero_gp3 = 4
 zero_gp4 = 6
 zero_gp5 = 8
 zero_gp6 = 10
 zero_gp7 = 12
 zero_gp8 = 14
 zero_gp9 = 16
 zero_gp10 = 18
 user_reserved = &70
 irq_A = &A0
 fcount = &A1
 RAM_size = &A3
 jobout_buf = &400
 jobin_buf = &480
 OS_PRINTER = &FFB9
 OS_CALLOS = &FFBC	
 OS_SENDBYTE = &FFBF
 OS_READBYTE = &FFC2
 OS_SENDJOB = &FFC5
 OS_READJOB = &FFC8
 OS_DECODEJOB = &FFCB
 :
 FOR create=1 TO 2
 :
 FOR pass%=4 TO 7 STEP 3
 P%=&100*create : O%=data%
 [OPT pass%
 :
 .job_DemoJob
 CMP #1
 BEQ DemoJob_go
 LDA #1
 LDY #0
 RTS
 /
 .DemoJob_go




 :
 .internal_handle
 CMP #1			; Is it function call 1 (NameCode) ?
 BNE internal_handle_2		; No, check for function 2
 /
 LDA job_id			; Get our JOB ID number
 STA zero_gp3			; Place it in zero_gp3 for CALLOS
 LDX #job_names MOD 256	; Our Job name table (LSB)
 LDY #job_names DIV 256	; Our Job name table (MSB)
 LDA #2			; CALLOS function 2
 JSR OS_CALLOS			; Call CALLOS
 BCC internal_yes		; Found, go off and claim call
 LDA #1			; Not found, restore A
 JMP (ovec)			; Call back to old vector
 /
 .internal_handle_2
 CMP #2			; Is it function call 2 (CodeName) ?
 BNE internal_handle_3		; No, unknown, pass on
 /
 LDA job_id			; Get our JOB ID
 STA zero_gp3			; Place it in zero_gp3 for CALLOS
 LDX #job_names MOD 256	; Our Job name table (LSB)
 LDY #job_names DIV 256	; Our Job name table (MSB)
 LDA #3			; CALLOS function 3
 JSR OS_CALLOS			; Call CALLOS
 BCC internal_yes		; Found, go off and claim call
 LDA #2			; Not found, restore A
 JMP (ovec)			; Call back to old vector
 /
 .internal_yes
 LDA #0			; We want to claim call for some reason
 /
 .internal_handle_3
 JMP (ovec)			; Call back to old vector
 :
 .job_handle
 PHA				; Make sure to preserve A
 CPY call			; Check call wanted against base of ours
 BCC job_handle_no		; Less than our base, so definately not ours
 TYA
 SBC #no_of_calls		; Subtract number of calls we have
 CMP call			; Now compare with base
 BCC job_handle2		; Yes, one of ours
 /
 .job_handle.no
 PLA				; Restore A
 JMP (ovec2)			; Call back to old vector
 /
 .job_handle2
 TYA
 SEC
 SBC call			; Subtract our base from call
 ASL A				; Times by 2 for offset into table
 TAY				; Place in Y
 LDA job_run_table,Y		; Get LSB of routine
 STA zero_gp1			; Store it for indirect jump (LSB)
 LDA job_run_table+1,Y		; Get MSB of routine
 STA zero_gp1+1		; Store it for indirect jump (MSB)
 PLA				; Restore A
 JMP (zero_gp1)		; Call our relevant routine
 :
 .job_names			; List of our JobNames
 EQUS "DemoJob":EQUB 13	; Test job name
 /
 EQUB &FF			; &FF - marks end of table
 :
 .job_run_table		; List of routine addresses to match Jobs
 EQUW job_DemoJob		; Test job routine
 :
 .call   : EQUB 0		; Store for our JobCode base
 .job_id : EQUB 0		; Store for our JobId
 .ovec   : EQUW 0		; Store for old internal_vec value
 .ovec2  : EQUW 0		; Store for old unknownjob_vec value
 :
 EQUS STRING$(&100-(P% MOD 256),CHR$0)
 .end_of_code			; end_of_code should be on page boundry
 :
 .execute
 LDX #no_of_calls		; Number of JobCodes wanted
 LDA #1			; CALLOS, function 1, Claim JobCodes
 JSR OS_CALLOS			; Call CALLOS
 STX call			; Store base address of JobCodes allocated
 STY job_id			; Store JOB ID given
 CPX #0			; Check we were given some codes
 BEQ noinstall			; No, don't bother installing ourself
 /
 LDA internal_vec		; Get old internal_vec (LSB)
 STA ovec			; Store it
 LDA internal_vec+1		; Get old internal_vec (MSB)
 STA ovec+1			; Store it
 LDA #internal_handle MOD 256	; Our internal handler (LSB)
 STA internal_vec		; Place it in the vector (LSB)
 LDA #internal_handle DIV 256	; Our internal handler (MSB)
 STA internal_vec+1		; Place it in the vector (MSB)
 /
 LDA unknownjob_vec		; Get old unknownjob_vec (LSB)
 STA ovec2			; Store it
 LDA unknownjob_vec+1		; Get old unknownjob_vec (MSB)
 STA ovec2+1			; Store it
 LDA #job_handle MOD 256	; Our unknown job handler (LSB)
 STA unknownjob_vec		; Place it in the vector (LSB)
 LDA #job_handle DIV 256	; Our unknown job handler (MSB)
 STA unknownjob_vec+1		; Place it in the vector (MSB)
 /
 LDX #end_of_code MOD 256	; End of code (LSB)
 LDY #end_of_code DIV 256	; End of code (MSB)
 STX jobin_buf			; Place it in jobin_buf (LSB)
 STY jobin_buf+1		; Place it in jobin_buf+1 (MSB)
 LDA #65			; JobCode for WriteLomem
 JSR OS_DECODEJOB		; Call JobCall handler
 /
 .noinstall
 RTS				; End - return to OS
 :
 .execcall
 LDA #>(execute-address)	; Final execution address offset (LSB)
 STA zero_gp1
 LDA #<(execute-address)	; Final execution address offset (MSB)
 STA zero_gp1+1
 /
 LDA #>(BitMap-address)	; BitMap address offset (LSB)
 STA zero_gp2
 LDA #<(BitMap-address)	; BitMap address offset (MSB)
 STA zero_gp2+1
 /
 LDA #>(execcall-address)	; Length of data to relocate (LSB)
 STA zero_gp3
 LDA #<(execcall-address)	; Length of data to relocate (MSB)
 STA zero_gp3+1
 /
 TXA				; Execcall REAL address
 SEC
 SBC #>(execcall-address)	; Subrtract length
 TAX				; Put it back in X
 TYA				; Ditto for MSB
 SBC #<(execcall-address)
 TAY
 LDA #4			; CALLOS function for Relocate
 JMP OS_CALLOS			; Call CALLOS
 /
 .BitMap			; BitMap start
 :
 ]
 NEXT
 :
 OSCLI "SAVE code"+STR$create+" "+STR$~data%+" "+STR$~O%
 :
 NEXT

Changelog

bob	071	Fixed MotorForward bug (%10)
bob	072	CallOS 14 now checks current adc.owner status
jim	072.d1	* Rewrite for new hardware/processor *
jim	072.d1	Moved vectors/workspace to &440
jim	072.d1	Moved rs.inp.buf to &500
jim	072.d1	Moved jobout.buf to &600
jim	072.d1	Moved jobin.buf to &680
jim	072.d1	Moved jobs.status to &700
jim	072.d1	Added irq.X (&A4)
jim	072.d1	Added irq.Y (&A5)
jim	072.d1	Removed short detection code
jim	072.d1	Voided CallOS' 6,7,8,9,10,11,13,16,17,18,19
jim	072.d1	Removed JobCalls ReadADCReg,WriteADCReg
jim	072.d1	Removed JobCalls ReadACIAReg,WriteACIAReg
jim	072.d1	Removed JobCalls ReadVIAReg,WriteVIAReg,SetVIAHigh,SetVIALow
jim	072.d1	Added CallOS 21 Write Outputs
jim	072.d1	Added CallOS 22 Read Outputs added
jim	072.d1	Added CallOS 23 Read Inputs added
jim	072.d1	Added CallOS 24 Write Motors
jim	072.d1	Added CallOS 25 Read Motors
jim	072.d1	Added CallOS 26 Read Keypad
jim	072.d1	Expanded CallOS 5 information
jim	072.d1	Added JobCall IdentSystem to return CallOS 5 info
jim	072.d1	JobCalls now use appropriate CallOS'
jim	072.d1	OS.READBYTE no longer preserves A on CS
jim	072.d1	irq.vec,irq2.vec,nmi.vec now redundant
jim	072.d1	Renamed JobCall DownloadData to DownloadData38
jim	072.d1	Renamed JobCall UploadData to UploadData38
jim	072.d1	Renamed JobCall ExecuteCode to ExecuteCode38
jim	072.d1	Renamed JobCall ReadByte to ReadByte38
jim	072.d1	Renamed JobCall StoreByte to StoreByte38
jim	072.d1	JobCall ForcedADCRead now checks adc.owner first
jim	072.d3	Added CallOS 27 Write printer
jim	072.d3	Added CallOS 28 Read printer
jim	072.d3	Added CallOS 29 Write RTC Reg
jim	072.d3	Added CallOS 30 Read RTC Reg
jim	072.d3	Added CallOS 31 Write RTC string
jim	072.d3	Added CallOS 32 Read RTC string
jim	072.d3	Added CallOS 33 Write RTC bcd
jim	072.d3	Added CallOS 34 Read RTC bcd
jim	072.d3	Added CallOS 35 Write LCD Reg
jim	072.d3	Added CallOS 36 Read LCD Reg
jim	072.d3	Added JobCall WritePrinter
jim	072.d3	Added JobCall ReadPrinter
jim	072.d3	Added JobCall PrintChar
jim	072.d3	Added JobCall PrintStreamZ
jim	072.d3	Added JobCall PrintStream
jim	072.d3	Added JobCall PrintServer
jim	072.d3	Added JobCall WriteRTCReg
jim	072.d3	Added JobCall ReadRTCReg
jim	072.d3	Added JobCall WriteRTC
jim	072.d3	Added JobCall ReadRTC
jim	072.d3	Added JobCall WriteRTCbcd
jim	072.d3	Added JobCall ReadRTCbcd
jim	072.d3	Added JobCall WriteLCDReg
jim	072.d3	Added JobCall ReadLCDReg
jim	072.d3	Added CallOS 37 Write Power ctrl
jim	072.d3	Added CallOS 38 Read Power ctrl
jim	072.d3	Implemented OS.PRINTER
jim	072.d3	Added JobCall PatchMF (MotorForward)
jim	072.d3	Changed irq.vec to int0.vec
jim	072.d3	Changed irq2.vec to int1.vec
jim	072.d3	Added int2irq.vec (wrksp+&20)
jim	072.d3	Added int3irq.vec (wrksp+&22)
jim	072.d3	Added int4irq.vec (wrksp+&24)
jim	072.d3	Added c0irq.vec (wrksp+&26)
jim	072.d3	Added c1irq.vec (wrksp+&28)
jim	072.d3	Added t1irq.vec (wrksp+&2A)
jim	072.d3	Added t2irq.vec (wrksp+&2C)
jim	072.d3	Added txirq.vec (wrksp+&2E)
jim	072.d3	Added tyirq.vec (wrksp+&30)
jim	072.d3	Added s1rirq.vec (wrksp+&32)
jim	072.d3	Added s1rirq.vec (wrksp+&34)
jim	072.d3	Added s2irq.vec (wrksp+&36)
jim	072.d3	Added adcirq.vec (wrksp+&38)
jim	072.d3	Renamed JobCall DownloadData38 to DownloadData740
jim	072.d3	Renamed JobCall UploadData38 to UploadData740
jim	072.d3	Renamed JobCall ExecuteCode38 to ExecuteCode740
jim	072.d3	Renamed JobCall ReadByte38 to ReadByte740
jim	072.d3	Renamed JobCall StoreByte38 to StoreByte740
jim	072.d4	Hardware update
jim	072.d4	Re-assigned CallOS 37 to Write Power/Charge control
jim	072.d4	Re-assigned CallOS 38 to Read Power/Charge control
jim	072.d4	Moved int2irq.vec to wrksp+&22
jim	072.d4	Moved int3irq.vec to wrksp+&24
jim	072.d4	Moved int4irq.vec to wrksp+&26
jim	072.d4	Moved c0irq.vec to wrksp+&28
jim	072.d4	Moved c1irq.vec to wrksp+&2A
jim	072.d4	Moved t1irq.vec to wrksp+&2C
jim	072.d4	Moved t2irq.vec to wrksp+&2E
jim	072.d4	Moved txirq.vec to wrksp+&30
jim	072.d4	Moved tyirq.vec to wrksp+&32
jim	072.d4	Moved s1rirq.vec to wrksp+&34
jim	072.d4	Moved s1rirq.vec to wrksp+&36
jim	072.d4	Moved s2irq.vec to wrksp+&38
jim	072.d4	Moved adcirq.vec to wrksp+&3A
jim	072.d4	Added OS.LCDVDU (&FFB7) and lcdvdu.vec (wrksp+&20)
jim	072.d5	Re-assigned CallOS 37 to Write/Read Power/Charge control
jim	072.d5	Re-assgined CallOS 38 Read Battery Voltage
jim	072.d5	OS.CALLOS now re-entrant
jim	072.d5	Fixed OS.PRINTER
jim	072.d5	Started to add internal logging software
jim	072.d5	Moved OS.LCDVDU to &FFB6
jim	072.d7	Added OS.PRINTERPOLL (&FFB3) and printerpoll.vec (wrksp+&3C)
bob	073	Matched jim & bob sensor lookup tables
bob	073	Added PatchMF
jim	072.dj	Added OS.CALLOS 51 Read keypad press
jim	072.dj	Added hard reset keypad press
jim	072.dk	Added JobCall ReadOutputs
jim	072.do	Changed soft reset of zero page locations
jim	072.do	Changed MotorForward/Backward bitmaps
jim	072.dp	Fixed OS.CALLOS WriteRTCbcd
jim	072.dq	Fixed OS.CALLOS ReadRTCstring for correct 24hr operation
jim	072.dr	Changed reset prompts
bob	074	Added support for Little Bob
jim	072.dy	Added further RTC/CMOS support
jim	072.dB	Removed double hard reset
jim	072.dE	Set _cpu.mode depending on external memory requirement
jim	072.dF	Added Insight code
jim	073	Sub-release
jim	074	Modification to LCD code - uses bsy flag all the time now
jim	074	Fixed OS.CALLOS 42 Write LCD Char Def
jim	074	Fixed OS.CALLOS 43 Read LCD Char Def
jim	075	Added simple battery charge code
jim	075	Fixed JobCall ReadSensorTable
jim	075	Fixed OS.CALLOS 42 Write LCD Char Def
jim	075	Fixed OS.CALLOS 43 Read LCD Char Def
jim	075	Added sleep code
jim	075	Added OS.CALLOS 52 Write Sleep Time
jim	075	Added OS.CALLOS 53 Read Sleep Time
jim	075	Modified battery charge code
jim	076	Modified JobCall ReadSensorTable
jim	076	Modified OS.CALLOS 26 ReadKeypad to do debounce
jim	077	Rewrote battery charging code
jim	077.2	Reduced stop charge threshold to 2
jim	077.3	Increased stop charge threshold to 3
jim	077.3	Added battery voltage averaging
bill	077	Stripped
bill	077	Moved ins to out port
bill	077	Added RTS
bill	077	Changed sensor ID system back to original, using p2.1
bill	077	Dedicated USB/RS selection line, RTS provisioned on p2.3
bill	077	Overload "bounce" timing
bill	078	CTS added
bill	079	Temperature fudge