Skip to content

TEC-OS12

12. Use the board's devices ​

The BDOS handles files and console text. TEC-OS's numbered ROM calls also let your program light the digits, sound the speaker, read the clock or write the board's displays. A signature and version check comes before those calls so the program can still end safely on another CP/M system.

Numbered calls and shared ports ​

A hardware port is an address used by Z80 IN and OUT instructions to read or write a device. It is separate from a memory address. Direct port writes can affect devices TEC-OS is using at the same time.

Port 01h selects a 7-segment digit with bits 0–5, drives the speaker with bit 7 and carries serial transmission with bit 6. Leaving bit 6 low during a digit scan or tone sends noise on a possible console connection. The PICO9918 can also be the console so changing its registers can disrupt terminal output. The ROM calls keep the serial line idle during digit and tone output and record display claims before graphics access.

These numbered services are routines already in ROM. They are different from a resident service such as GLCD.SVC, which is a file installed in RAM. A numbered call selects a routine through an entry address. A resident service can itself make such calls. Chapter 14 explains resident programs.

The ABI and its entry address ​

An application binary interface, or ABI, specifies entry addresses, register arguments and results that compiled programs can use. TEC-OS's fixed interface occupies eight bytes at C003h, after the reset jump:

AddressNameContents
C003hABITABthe letters TNA
C006hABIMAJthe major version, 5
C007hABIMINthe minor version, 1
C008hABICALLa jump: the numbered services

The three letters TNA are the signature. They distinguish this table from unrelated bytes at the same address on another system. ABIMAJ and ABIMIN give its ABI version, 5.1. That is separate from the ROM's product version, which INFO reports later.

A program checks the signature and major version before calling C008h. The major must be exactly 5. A new major may renumber services while a new minor adds numbers at the end. Code using the 5.0 calls can therefore run on 5.1. A program requiring an added minor-version call must check that version too.

ABICALL is a jump into the dispatcher, not a table of separate fixed routine addresses. The selected routines can move between builds. Their numbers, argument contracts and the fixed ABI entry remain the interface. The reset jump itself also has a fixed location at C000h.

The ROM assembles from src/abi.inc, whose public copy is dist/abi.inc. The examples include it as ABI.INC for names such as ABICALL and TNLCD. These constants define no executable bytes. %INCLUDE inserts the named file's contents into the assembly source.

Registers, status and stack ​

Chapter 9 introduced byte registers and register pairs. For a numbered call, C selects the service. B is a byte argument and DE or HL can hold word values or buffer addresses according to that service's contract. A carries no argument because the dispatcher uses it during selection. The program calls ABICALL after loading those inputs.

The nine hardware calls, numbers 0–8, return status in A with these flags:

StatusFlagsMeaning
0zero set, carry clearsuccess
1carry setno such service, an argument out of range or a request rejected with nothing changed
2carry setthe device isn't fitted or isn't the console

JP NZ or JP C detects a hardware-call failure. The documented result registers hold the outputs. Save any other values the program needs across the call rather than depending on their previous contents. Hardware calls preserve IX, IY and the alternate registers. Appendix B gives each complete register contract.

A number from TNCOUNT, 17, upwards returns status 1 with B, DE and HL unchanged. The hardware numbers identify these routines:

Numberabi.incDoes
0TNINFOreports the board's devices, the ROM's version and the console's size
1TNDIGITmakes one pass over the six 7-segment digits
2TNLCDwrites one row of the 20 × 4 LCD
3TNVIDEOclaims or releases the PICO9918
4TNTONEsounds the speaker
5TNJOYreads the joystick
6TNKEYSreads the matrix keyboard's rows
7TNPUTATwrites text at a screen position on a video console
8TNRTCreads or sets the DS1302 clock

The other numbers extend the same ABI. Numbers 9–11 mount, unmount and describe volumes, as Chapter 18 explains. Number 12 registers the GLCD video-driver table. Numbers 13 and 14 find or enumerate named records in the registry described in Chapter 14. Those registry calls have their own result convention, including a generation in A, so their callers test carry rather than applying the hardware status rule. Number 15 returns the ROM font and was added in ABI 5.1. Number 16, TNSHELL, sets the shell hook that Chapter 21 describes (src/abi.inc:208-236).

The hardware services use the caller's stack. PUTAT has the largest bound: 22 bytes below the return address supplied by CALL. That fits the inherited 30-byte allowance from Chapter 11 but a caller must also count its own nested calls and saved registers. Appendix B lists the individual bounds. These are bounds, not newly measured peaks.

None of the nine hardware services calls the BDOS. They can be called from code already inside a BDOS call, such as a BIOS-table hook. This does not apply to every numbered service: MOUNT and UNMNT call BDOS function 37 and must be called from outside an active BDOS call. Their register-preservation exceptions are also listed in Appendix B.

The shared signature check and print routines ​

CHKHW compares four bytes: TNA followed by major version 5. It returns with Z set only when all four match. On failure the examples branch to NOTHW, which prints a message through BDOS function 9 and ends without using a hardware port.

The examples share this complete HWLIB.ASM. It contains both code and data so each program includes it after its own entry code. SAY prints a dollar-terminated string. A CALL to SAY returns to its caller while a top-level jump to it prints and ends through the inherited return word. LABHEX prints a label followed by A in hexadecimal. It saves A and HL across the string output. A nibble is four bits, half a byte. HEX prints the two nibbles through NIBBLE.

A binary-coded decimal byte, or BCD byte, stores one decimal digit in each nibble. Printing its two nibbles as hexadecimal also displays those decimal digits. The clock examples use that representation.

asm
; HWLIB.ASM: routines this chapter's programs share. It goes after a
; program's own code, since it makes bytes where it is included.

; Test for TEC-OS with ABI 5. Returns Z if the table's four bytes match,
; NZ if not. Changes AF, B, DE and HL.
CHKHW:	LD	HL,ABITAB	; signature, then the major version
	LD	DE,TAG		; what they must be
	LD	B,4		; a major of 6 fails as a wrong letter does
CHKH1:	LD	A,(DE)
	CP	(HL)
	RET	NZ		; NZ: not what TAG holds
	INC	HL		; on to the next of each
	INC	DE
	DJNZ	CHKH1
	RET			; Z from the last comparison

; End a program that found another system. Takes nothing, and returns to
; the CCP through the program's own return address.
NOTHW:	LD	DE,NOTMSG	; no board devices here, and no harm done
; Print the $-ended string at DE. A jump here prints and ends the program.
; Changes AF, BC, DE and HL.
SAY:	LD	C,9		; BDOS print string
	JP	BDOS		; its RET is this routine's

; Print the $-ended label at DE, then A in hex. Changes AF, BC and DE.
LABHEX:	PUSH	AF		; the value, across the BDOS call
	PUSH	HL		; the BDOS changes HL
	CALL	SAY
	POP	HL
	POP	AF		; on into HEX
; Print A as two hex digits, which also prints a BCD byte in decimal.
; Changes AF, BC and DE.
HEX:	PUSH	HL		; a caller may keep a value here
	PUSH	AF		; the low digit comes second
	RRCA			; the high digit down to bits 0 to 3
	RRCA
	RRCA
	RRCA
	CALL	NIBBLE		; the high digit
	POP	AF
	CALL	NIBBLE		; the low digit
	POP	HL
	RET

; Print the low four bits of A as a hex digit. Changes AF, BC, DE and HL.
NIBBLE:	AND	$0F		; one digit
	ADD	A,'0'
	CP	'9'+1		; past 9: the letters start seven later
	JR	C,NIBB1
	ADD	A,7
NIBB1:	LD	E,A		; BDOS console output
	LD	C,2
	JP	BDOS		; its RET is this routine's

TAG:	DB	"TNA",5
NOTMSG:	DB	"not TEC-OS with ABI 5",13,10,"$"

The programs below assemble with the starter card's TAS.

The board's report ​

INFO, number 0, reports device state without taking an argument. It returns B's device bits, the ROM's version in DE and the console's size in HL. L is the column count and H the row count. A program checks these results before using an optional device. B's defined bits are:

B bitValueSet when
001hthe matrix keyboard is enabled by the board's Matrix switch
102ha PICO9918 answered at cold boot
204hGLCD.SVC is in
308ha DS1302 clock answered at cold boot
410hthe PICO9918 is claimed
780hthe ROM's word GLCD? is on LCD row 3

D holds the ROM version's major and E its minor so 0001h means version 0.1 rather than ABI 5.1. BOARD prints the report in hexadecimal and then calls TNCOUNT, the first number past the last service, to demonstrate the out-of-range status:

asm
; BOARD: print what INFO reports, then what a bad number returns.
BDOS	EQU	$0005
%INCLUDE "ABI.INC"

	ORG	$0100
	CALL	CHKHW		; the numbers below hold only on ABI 5
	JP	NZ,NOTHW
	LD	C,TNINFO
	CALL	ABICALL
	PUSH	HL		; the size, across the printing
	PUSH	DE		; and the version
	LD	A,B		; the device bits
	LD	DE,MSGB		; the first line
	CALL	LABHEX
	POP	HL		; the version, high byte first
	LD	A,H
	LD	DE,MSGVER	; the second
	CALL	LABHEX
	LD	A,L		; then the low byte on the same line
	CALL	HEX
	POP	HL		; the console's size
	PUSH	HL		; the rows, for the next line
	LD	A,L		; the columns
	LD	DE,MSGCOL	; the third
	CALL	LABHEX
	POP	HL		; the rows
	LD	A,H
	LD	DE,MSGROW	; the fourth
	CALL	LABHEX
	LD	C,TNCOUNT	; the number after the last service
	CALL	ABICALL
	PUSH	AF		; the status, and its carry
	LD	DE,MSGBAD
	CALL	LABHEX
	POP	AF		; only the carry is wanted now
	LD	DE,MSGNC
	JP	NC,SAY		; prints and ends the program
	LD	DE,MSGC		; the carry was set
	JP	SAY		; prints and ends the program

; What each line starts with, and how the last ends.
MSGB:	DB	"Devices  B = $"
MSGVER:	DB	13,10,"Version DE = $"
MSGCOL:	DB	13,10,"Columns  L = $"
MSGROW:	DB	13,10,"Rows     H = $"
; The last line: the status, then its carry.
MSGBAD:	DB	13,10,"Number 17: status $"
MSGC:	DB	", carry set",13,10,"$"
MSGNC:	DB	", carry clear",13,10,"$"
%INCLUDE "HWLIB.ASM"

These host commands place ABI.INC, HWLIB.ASM and BOARD.ASM beside each other on a copy of the starter card. TAS uses the included files on the card when it assembles BOARD:

sh
cp dist/starter.img hw.img
node tools/tecvol.mjs put hw.img B dist/abi.inc --text
node tools/tecvol.mjs put hw.img B hwlib.asm --text
node tools/tecvol.mjs put hw.img B board.asm --text

Each later example's source goes on hw.img in the same way. TAS BOARD reads BOARD.ASM on the current drive and writes BOARD.COM beside it. TAS.COM is on A so the program search described in Chapter 5 finds it from B. In the serial-model configuration, the clock set INFO's bit 3 and the console size was zero:

text
TEC-OS 0.1 for TEC-1G
A: SYSTEM
B: WORK
C: PROJECTS

A>SHELL.SVC

A:SYSTEM>GLCD.SVC

A:SYSTEM>B:
B:WORK>TAS BOARD
BOARD.COM written

B:WORK>BOARD
Devices  B = 08
Version DE = 0001
Columns  L = 00
Rows     H = 00
Number 17: status 01, carry set

B:WORK>

The --board pico model configuration includes the PICO9918, graphics LCD and matrix keyboard. Cold boot selected the PICO console at 40 columns. Its device bits were 0Bh and its size was 28h by 18h (40 by 24). The startup GLCD.SVC line installed nothing while PICO was the console. This is a host-model run, not a browser capture:

text
TEC-OS 0.1 for TEC-1G
A: SYSTEM
B: WORK
C: PROJECTS

A>SHELL.SVC

A:SYSTEM>GLCD.SVC

A:SYSTEM>B:
B:WORK>TAS BOARD
BOARD.COM written

B:WORK>BOARD
Devices  B = 0B
Version DE = 0001
Columns  L = 28
Rows     H = 18
Number 17: status 01, carry set

B:WORK>

In the GLCD-only configuration, --board glcd, GLCD.SVC installed and became the console. The same report gave device bits 0Dh, columns 15h and rows 08h: 21 by 8.

A screen-layout program uses the size INFO returns. With a serial console, both values are zero because TEC-OS cannot read the terminal's dimensions. The program can use an 80-by-24 assumption, as the CCP does for pagination. A console change can alter the values. After CON UC1 selects the graphics LCD, INFO gives 21 by 8 so programs that change console must read them again.

The LCD and the speaker ​

The 20 × 4 character LCD is separate from the console. LCD, number 2, writes exactly 20 characters from HL to row B, numbered 0–3. Shorter messages need space padding. B of 4 or more returns status 1 before any write. LCDMSG uses row 1 and then calls TONE, number 4, for a beep:

asm
; LCDMSG: write a row of the 20 x 4 LCD and beep, on TEC-OS only.
BDOS	EQU	$0005
%INCLUDE "ABI.INC"

	ORG	$0100
	CALL	CHKHW		; the numbers below hold only on ABI 5
	JP	NZ,NOTHW
	LD	B,1		; the LCD's second row
	LD	HL,TEXT		; exactly 20 characters
	LD	C,TNLCD
	CALL	ABICALL		; A is the status, 0 for this row
	LD	B,120		; about 1 kHz
	LD	DE,400		; 400 half cycles, about 0.2 s
	LD	C,TNTONE
	CALL	ABICALL		; returns when the tone ends
	LD	DE,DONE
	JP	SAY		; prints and ends the program

TEXT:	DB	"Hello on the LCD    "
DONE:	DB	"Written and beeped",13,10,"$"
%INCLUDE "HWLIB.ASM"

This run requested the character LCD's rows with --lcd:

text
TEC-OS 0.1 for TEC-1G
A: SYSTEM
B: WORK
C: PROJECTS

A>SHELL.SVC

A:SYSTEM>GLCD.SVC

A:SYSTEM>B:
B:WORK>TAS LCDMSG
LCDMSG.COM written

B:WORK>LCDMSG
Written and beeped

B:WORK>
--- LCD
TEC-OS 0.1  TEC-1G
Hello on the LCD
B: WORK

The same run's LCD text showed the message on its second row. The other rows retained cold-boot text:

text
TEC-OS 0.1  TEC-1G
Hello on the LCD
B: WORK

The row remains after program exit. On another ABI major, the signature check takes NOTHW instead, printing not TEC-OS with ABI 5 and leaving the character LCD untouched.

Row 3 has a separate ownership mark for the ROM's GLCD? message. When GLCD.SVC installs, TEC-OS clears that message only while the mark is set. An LCD call writing row 3 clears the mark. Later driver installation therefore preserves the program's replacement text. Chapter 8 explains the startup message.

TONE toggles port 01h bit 7. B controls the delay and DE the number of half cycles. Between ordinary toggles the delay is 16 × B + 51 T-states. A full cycle takes two such intervals. At 4 MHz the frequency is therefore 2,000,000 divided by that sum.

With B = 120, the interval is 1,971 T-states and the frequency is about 1,015 Hz. DE = 400 gives about 0.2 seconds, as in LCDMSG. For a duration in seconds, the half-cycle count is frequency × 2 × seconds. B = 0 counts as 256, giving about 482 Hz, while B = 1 gives about 29,851 Hz. DE = 0 returns immediately without sound. These are instruction-derived relationships. Appendix B distinguishes the final interval and call overhead.

TONE blocks until its half cycles finish so the caller does no other work during the sound. A nonempty tone ends with the speaker off. The routine keeps the serial transmit line idle while driving the speaker.

The digits and the clock ​

The six 7-segment digits share their segment wiring. Port 02h sets the pattern and port 01h selects which digit lights. A steady-looking display requires repeated scans, with each digit lit only during its own turn.

DIGIT, number 1, scans once through six pattern bytes at HL, leftmost first. It turns every digit off at the end. On the host model it takes 20,660 T-states from the call target to return, about 5.2 ms at 4 MHz. Its port writes put 3,348 T-states, about 0.84 ms, between selecting each digit and turning it off. The caller repeats the scan and supplies its own number-to-segment table.

RTC, number 8, transfers seven BCD bytes through HL: seconds, minutes, hours, date, month, weekday and year. B = 0 reads and B = 1 sets them. Setting ends with the clock's write protection enabled. Any other B gives status 1 without changing the clock. A failed clock probe gives status 2. INFO bit 3 separately records whether the clock answered at cold boot.

DIGCLK repeats DIGIT and checks for a key with BDOS function 6. Every 32nd pass it first reads the clock and converts hours, minutes and seconds into six patterns. Its initial counter value makes the first pass read too:

asm
; DIGCLK: the time on the 7-segment digits, until a key.
BDOS	EQU	$0005
%INCLUDE "ABI.INC"

	ORG	$0100
	CALL	CHKHW		; the numbers below hold only on ABI 5
	JP	NZ,NOTHW
LOOP:	LD	HL,PASSES	; the clock is read one pass in 32
	INC	(HL)
	LD	A,(HL)
	AND	31		; 0 on every 32nd pass
	CALL	Z,TOSEG
	LD	HL,DIGS		; one pass over the six digits
	LD	C,TNDIGIT
	CALL	ABICALL
	LD	E,$FF		; BDOS direct input: a key, or 0
	LD	C,6
	CALL	BDOS
	OR	A
	JR	Z,LOOP		; no key: the digits need another pass
	RET			; DIGIT left them dark

; Read the clock and turn hours, minutes and seconds into six patterns at
; DIGS. Changes AF, BC, DE and HL; with no clock DIGS stays as it was.
TOSEG:	LD	HL,TIME		; seven BCD bytes, seconds first
	LD	B,0		; B = 0: read
	LD	C,TNRTC
	CALL	ABICALL
	RET	NZ		; status 2: no clock
	LD	DE,DIGS		; the leftmost digit's pattern first
	LD	A,(TIME+2)	; hours
	CALL	PAIR
	LD	A,(TIME+1)	; minutes
	CALL	PAIR
	LD	A,(TIME)	; seconds, and PAIR's RET ends TOSEG
; Store the two digits of the BCD byte in A as patterns at DE, which moves
; on by two. Changes AF, BC and HL.
PAIR:	PUSH	AF		; the units digit, for after the tens
	RRCA			; the tens digit down to bits 0 to 3
	RRCA
	RRCA
	RRCA
	CALL	SEG		; the tens
	POP	AF		; on into SEG for the units
; Store the low four bits of A as a pattern at DE, which moves on. Changes
; AF, BC and HL.
SEG:	AND	$0F		; one digit
	LD	C,A		; BC = the digit
	LD	B,0
	LD	HL,SEGTAB	; the digit's segments, one bit each
	ADD	HL,BC
	LD	A,(HL)
	LD	(DE),A		; into the row of six
	INC	DE
	RET

; The digits 0 to 9 as segment patterns.
SEGTAB:	DB	$EB,$28,$CD,$AD	; 0 to 3
	DB	$2E,$A7,$E7,$29	; 4 to 7
	DB	$EF,$AF		; 8 and 9
PASSES:	DB	31		; 31: the first pass reads the clock
DIGS:	DB	0,0,0,0,0,0
TIME:	DS	7
%INCLUDE "HWLIB.ASM"

DIGIT leaves the digits dark between calls. Other work in the loop reduces the time they are lit and can produce visible flicker if it takes too long. DIGCLK does a BDOS input check each pass and a clock read every 32nd pass. When it exits, the final DIGIT call has already turned the digits off.

NOW reads the same seven-byte clock buffer and prints its fields in date order. The clock stores only two year digits so this example supplies the century text 20 itself:

asm
; NOW: print the date and time from the clock.
BDOS	EQU	$0005
%INCLUDE "ABI.INC"

	ORG	$0100
	CALL	CHKHW		; the numbers below hold only on ABI 5
	JP	NZ,NOTHW
	LD	HL,TIME		; seven BCD bytes
	LD	B,0		; B = 0: read
	LD	C,TNRTC
	CALL	ABICALL
	LD	DE,NOCLK
	JP	NZ,SAY		; status 2: no clock answered
	LD	DE,CENTY	; the clock holds two digits of the year
	CALL	SAY
	LD	HL,PLAN		; each field's offset, then what follows it
NEXT:	LD	C,(HL)		; BC = the field's offset in TIME
	LD	B,0
	INC	HL
	PUSH	HL		; the plan, across the printing
	LD	HL,TIME
	ADD	HL,BC
	LD	A,(HL)		; a BCD byte prints as decimal
	CALL	HEX
	POP	HL
	LD	E,(HL)		; the separator
	INC	HL
	PUSH	HL		; the plan again
	LD	C,2		; BDOS console output
	CALL	BDOS
	POP	HL
	LD	A,(HL)		; another field?
	INC	A
	JR	NZ,NEXT		; FFh ends the plan
	LD	DE,ENDLN
	JP	SAY		; prints and ends the program

PLAN:	DB	6,'-',4,'-'	; year, month
	DB	3,' ',2,':'	; date, hours
	DB	1,':',0,' '	; minutes, seconds
	DB	5,13,$FF	; weekday, 1 to 7; FFh ends the plan
CENTY:	DB	"20$"
NOCLK:	DB	"no clock",13,10,"$"
ENDLN:	DB	10,"$"		; after the plan's carriage return
TIME:	DS	7
%INCLUDE "HWLIB.ASM"

PLAN contains field-offset/separator pairs. NOW uses each offset to select a BCD byte, prints it through HEX and then prints its separator. The TAS and run capture was:

text
TEC-OS 0.1 for TEC-1G
A: SYSTEM
B: WORK
C: PROJECTS

A>SHELL.SVC

A:SYSTEM>GLCD.SVC

A:SYSTEM>B:
B:WORK>TAS NOW
NOW.COM written

B:WORK>NOW
2026-11-01 12:00:09 07

B:WORK>

That model run began with its clock at 12:00 on 1 November 2026. The clock advances with emulated T-states, including those used while TAS assembles. Its weekdays start at 1 for Monday so the captured Sunday is 07.

The keys and the joystick ​

Character input reports keys one at a time. A game may instead need the matrix switches held down during a scan. KEYS, number 6, stores eight row bytes at HL, row 0 first, with a 1 for each pressed switch. JOY, number 5, returns active joystick lines as 1 bits in B.

KEYS and JOY always return status 0. They do not test the Matrix switch or report device absence. ROWS therefore checks INFO bit 0 before waiting for a key, then prints the matrix rows and joystick byte:

asm
; ROWS: wait for a key on the matrix keyboard, then print the eight rows
; and the joystick's byte.
BDOS	EQU	$0005
%INCLUDE "ABI.INC"

	ORG	$0100
	CALL	CHKHW		; the numbers below hold only on ABI 5
	JP	NZ,NOTHW
	LD	C,TNINFO	; is there a matrix keyboard to wait for?
	CALL	ABICALL
	BIT	0,B		; INFO's matrix bit
	LD	DE,NOMTX
	JP	Z,SAY		; none: the wait would never end
WAIT:	CALL	SCAN		; until a key is down
	JR	Z,WAIT
	LD	C,TNJOY		; the same moment, read as the joystick
	CALL	ABICALL
	LD	A,B		; its bits
	LD	(STICK),A	; printed after the rows
	LD	HL,BUF		; the eight rows, on one line
	LD	B,8
SHOW:	LD	A,(HL)		; one row's byte
	INC	HL
	PUSH	BC		; the count, across the printing
	CALL	HEX
	LD	E,' '		; BDOS console output
	LD	C,2
	PUSH	HL		; the BDOS changes HL
	CALL	BDOS
	POP	HL
	POP	BC
	DJNZ	SHOW
	LD	A,(STICK)	; JOY's bits
	LD	DE,MSGJOY
	CALL	LABHEX
	LD	DE,CRLF		; end the line
	CALL	SAY
DRAIN:	CALL	SCAN		; end only when every key is up
	JR	NZ,DRAIN
	RET

; Read the rows into BUF. Returns Z if no key is down, NZ if one is.
; Changes AF, BC, DE and HL.
SCAN:	LD	HL,BUF		; eight bytes, row 0 first
	LD	C,TNKEYS
	CALL	ABICALL
	LD	HL,BUF		; OR the rows together
	LD	B,8
	XOR	A		; nothing seen yet
SCAN1:	OR	(HL)		; a key leaves a bit set
	INC	HL
	DJNZ	SCAN1
	OR	A		; Z if every row was 0
	RET

NOMTX:	DB	"no matrix keyboard",13,10,"$"
MSGJOY:	DB	" JOY $"
CRLF:	DB	13,10,"$"
BUF:	DS	8		; the rows, as KEYS leaves them
STICK:	DS	1		; JOY's byte
%INCLUDE "HWLIB.ASM"

The --board pico configuration has a matrix keyboard. Its @key input pressed x, which is in row 7:

text
TEC-OS 0.1 for TEC-1G
A: SYSTEM
B: WORK
C: PROJECTS

A>SHELL.SVC

A:SYSTEM>GLCD.SVC

A:SYSTEM>B:
B:WORK>TAS ROWS
ROWS.COM written

B:WORK>ROWS
00 00 00 00 00 00 00 08  JOY 00

B:WORK>

JOY reads port FEh with address line 11 low. KEYS selects row 3 in the same way so those inputs share wiring. In the alternate 5 run, the row was 00 00 00 01 00 00 00 00 JOY 01. A row-3 matrix key can therefore appear as a joystick bit. A joystick line also appears in row 3, including at the console. On the model, bit 0 types 5. Appendix B gives the diagnostic names for the joystick bits.

Claiming the PICO9918 ​

Before using PICO graphics, a program calls VIDEO, number 3, with B = 1 to claim the chip. B = 0 releases it. Other values give status 1 without changing the claim. When no PICO9918 answered at cold boot, both valid modes return status 2.

While the PICO is claimed, console output to it is discarded. The terminal also suppresses its cursor and page flips. The program can set a graphics mode and use the chip's video memory (VRAM) through data port BEh and control port BFh. Messages intended for the PICO must be printed before the claim or after release. The separate character LCD remains available during the graphics work.

A release restores the ROM PICO console's mode, font and displayed page when that console started at cold boot. If it is the selected console, the release also clears the screen and homes the cursor. With the Matrix switch disabled the PICO console did not start so release leaves the chip's programmed state alone. Warm boot first writes 80h to that chip's register 1, though, which blanks the display.

The claim records a flag rather than an owner identity. Warm boot releases it through the same path so an exiting program's claim ends too. Release with no active claim does nothing and returns status 0.

When the ROM PICO console started, it also registered a named PICO record. That record supplies the graphics entry table listed in Appendix B. The register, fill, write and read entries require a claim. Programs obtain the table through the registry rather than fixed internal ROM addresses. The record is version 4. It keeps version 3's width entry, which instead requires an unclaimed display.

FONT, number 15 in ABI 5.1, returns the ROM console font address in HL. It contains 96 characters from 20h, each eight bytes with the top row first. The selected data is TEC Compact. Each row has five pixels in bits 7–3. Both display drivers use this font. Its address is a result, not a fixed address for a program to embed.

DEMO below claims the PICO when INFO bit 1 is set, draws through the ports until a key is received and then releases it.

Text at a screen position ​

PUTAT, number 7, writes text at a position without moving the console cursor or scrolling. B is the count, HL the character address and D/E the zero-based row/column. It bypasses the escape-sequence parser and clips at the right edge.

A usable video console is required before any count or position succeeds. Serial/BAT output and a claimed PICO console return status 2, even for zero characters. On an available video console, a count of 0 returns status 0 without reading source bytes or changing the cursor image. For a nonzero count, PUTAT removes the cursor image before checking the row. A row below the screen writes nothing and returns status 0. The next input wait can draw the cursor again.

PLACE writes TEC-OS at two positions. The second string has bit 7 set in each byte so the display driver draws the reverse images:

asm
; PLACE: write a word at two screen positions with PUTAT, plain and in
; reverse, leaving the cursor where it is.
BDOS	EQU	$0005
%INCLUDE "ABI.INC"

	ORG	$0100
	CALL	CHKHW		; the numbers below hold only on ABI 5
	JP	NZ,NOTHW
	LD	DE,$0A1E	; row 10, column 30
	LD	HL,PLAIN
	CALL	WRITE
	LD	DE,$0B1E	; the row below
	LD	HL,INVERT
	CALL	WRITE
	LD	DE,MSG		; A holds the second call's status
	CALL	LABHEX
	LD	DE,CRLF		; end the line
	JP	SAY		; prints and ends the program

; Write the six characters at HL at row D, column E. Returns the status
; in A. Changes F, BC, DE and HL.
WRITE:	LD	B,6
	LD	C,TNPUTAT
	JP	ABICALL		; its RET is this routine's

PLAIN:	DB	"TEC-OS"
INVERT:	DB	$D4,$C5,$C3	; "TEC" with bit 7 set: reverse images
	DB	$AD,$CF,$D3	; "-OS", the same
MSG:	DB	"PUTAT status $"
CRLF:	DB	13,10,"$"
%INCLUDE "HWLIB.ASM"

PUTAT sends bytes directly to the display routine. Both the ROM PICO console and GLCD.SVC draw 80h–FFh as reverse images of 00h–7Fh. The ordinary video-console path drops those high bytes, as Chapter 17 explains. Serial output transmits all eight bits. This makes PUTAT a direct route to reverse characters without an escape sequence. The PICO text capture shows both strings alike because its decoder does not report reverse presentation:

text

TEC-OS 0.1 for TEC-1G
A: SYSTEM
B: WORK
C: PROJECTS

A>SHELL.SVC

A:SYSTEM>GLCD.SVC

A:SYSTEM>B:                   TEC-OS
B:WORK>TAS PLACE              TEC-OS
PLACE.COM written

B:WORK>PLACE
PUTAT status 00

B:WORK>

In this 40-column run the two copies went to rows 10 and 11 at column 30, over the ends of the earlier command lines there. The cursor did not move, so PLACE's status line and the next prompt appeared below them.

On the 8-row graphics LCD, PLACE's row 10 is below the display so it writes nothing and reports status 0. On serial it reports status 2. It also reports 2 while the PICO is both claimed and the console. A claim on PICO leaves PUTAT available on the graphics-LCD console. Portable code checks the ABI before PUTAT and retains a BDOS path for status 2.

On the host model at 80 columns a PICO PUTAT character costs about an eighth of BDOS function 2: one 61-character call takes 30,907 T-states, about 507 a character, and function 2 takes 4,030 for one. On the graphics LCD a call of 15 characters from row 3, column 2 takes about 35,750 T-states a character, against about 35,060 for function 2. Pixel drawing dominates both paths. Appendix B retains the per-cell figures and their conditions.

DEMO ​

DEMO combines INFO, TONE, LCD, VIDEO, DIGIT and RTC with its own drawing code. It reports devices, beeps with B = 120 and DE = 400 and writes all four character-LCD rows. When INFO bit 1 is set, it claims the PICO and sets Graphics I through the ports for colour bars and a bouncing ball.

Every 32nd loop reads the clock for LCD row 3. Each loop scans a hexadecimal counter onto the digits, scans the LED rows, moves the ball when graphics are active and polls for a key. On exit it sends dark digit/LED patterns and releases an active claim.

The source is programs/DEMO.ASM, and the one card, which the web page also runs, holds that source in place of DEMO.COM. TAS DEMO builds DEMO.COM on the card, DEMO then runs it and TED DEMO.ASM opens the source (tools/media.mjs:229-232, tools/media.mjs:282-283, test/demo.test.mjs:1-4, test/starter.test.mjs:191-216). This run builds and runs it on the starter card:

text
TEC-OS 0.1 for TEC-1G
A: SYSTEM
B: WORK
C: PROJECTS

A>SHELL.SVC

A:SYSTEM>GLCD.SVC

A:SYSTEM>TAS DEMO
DEMO.COM written

A:SYSTEM>DEMO

TEC-OS hardware services demo
PICO9918: yes   GLCD: no   clock: yes
Watch the LCD, the digits, the LEDs and the screen.
Press any key to stop.

Demo stopped.

A:SYSTEM>

Here GLCD: no reports INFO bit 2, not the physical absence of the display. The startup GLCD.SVC line installed nothing because PICO was the console. The transcript contains the console bytes sent during the run. The PICO showed its initial lines before the claim. The same run's text-screen dump after release was:

text

Demo stopped.

A:SYSTEM>

No hardware service scans the 8 × 8 LEDs. DEMO writes those ports itself, one pass over eight rows each loop. Appendix G gives their wiring. Those ports carry neither the serial transmit line nor console output so those writes do not require the shared-port state used by DIGIT or VIDEO.

TEC-OS and Thorium documentation