Files
sibo-playground/docs/reference/01-building-apps.md

23 KiB
Raw Permalink Blame History

Building Applications on the Psion SIBO / Workabout in C

A reference for developing C applications for the Psion SIBO family (HC, MC, Series 3/3a/3c, Siena, Workabout) using the SIBO C SDK and the TopSpeed C compiler.

Sourced from the SIBO 'C' Software Development Kit — General Programming Manual (v2.30, March 1999) and the SIBO Hardware Development Kit (May 1995) LDD sections. Claims are drawn only from those documents; where a point is not stated in the source it is marked as such.

Workabout MX note. The manual (SDK v2.30) names the Workabout and Siena but does not mention a distinct "Workabout MX" model. Everything here that applies to the Workabout applies to the MX at the SIBO/EPOC-16 level. Anything genuinely MX-specific is called out explicitly; unmarked material is generic SIBO and holds for the MX unless noted.


1. Architecture and programming model

A SIBO machine is "a battery-powered portable computer that is based on the SIBO architecture ... designed to minimise the size, weight and power consumption" (General, ch. 2). Key hardware components named by the manual:

  • A power-management system that "selectively powers subsystems under software control".
  • Solid State Disks (SSDs) — "fast low-power silicon-based mass storage with no moving parts".
  • Asynchronous serial interface running at "Mega bit rates".
  • "An 8086 class of processor (or any compatible processor such as an 80286)" (General, ch. 2).
  • Hardware protection: "address trapping of out-of-range writes and a watch-dog timer on interrupts being disabled".
  • Real-time clock, ROM-resident system software, graphics LCD, and (on some models) a touch-sensitive digitising pad.

The hardware is "primarily implemented in custom ICs called ASICs ... surface-mounted static CMOS ICs throughout."

CPU note / uncertainty. The manual describes the CPU only as "an 8086 class of processor (or any compatible processor such as an 80286)". The commonly cited V30-class part (NEC V30 / V30H, an 8086-compatible) is not named in these source documents; treat "V30-class" as background, not a manual quotation. The programming-relevant fact is what the manual does state: it is an 8086-class, 16-bit, real-mode-segmented CPU with no 8087.

The operating system is EPOC (here the 16-bit EPOC that runs on SIBO; not the later 32-bit EPOC32). Features listed (General, ch. 2):

  • Preemptive multi-tasking; MS-DOS-compatible and installable file systems.
  • Asynchronous services and client-server architecture (file server, window server).
  • A comprehensive I/O system with many built-in I/O devices and dynamically loadable device drivers.
  • A reentrant function library; "multiple processes of the same program share a single copy of the code"; code-shared dynamic link libraries.

The terms are distinguished in the manual: SIBO = the hardware architecture; EPOC = the operating system designed for it. On SIBO machines "the system software resides on an in-built ROM."

Fatal errors (panics)

When the system detects a condition it believes can only be a bug, it "terminates the process with a 'panic number' in the range 0 to 255 ... There is no way for applications to avoid being terminated when a panic has been started." Panic-number ranges (General, ch. 2):

Range Source
080, and 255 PLIB library
81129 Window Server library
130160 OLIB object library
160254 non-ROM code (e.g. the ISAM library)

panic 80 specifically often means a program "failed to locate SYS$8087.LDD" (the floating-point emulator LDD) — see §5.

Why TopSpeed C (small model)

Applications are written, compiled and linked on a PC and then run/debugged on the SIBO machine. The compiler and linker are the TopSpeed C system (Clarion Software Corporation). The SDK requires the small code model of TopSpeed:

"#model small jpi — The code is to be compiled in small model (code and data segments each restricted to 64K), with the jpi (TopSpeed C) convention of using registers to pass parameters to subroutines." (General, ch. 3)

You may install other TopSpeed code models, but "they will not be used by the SIBO SDK." Small model matches the 8086-class segmented architecture: a single 64K code segment and a single 64K data segment, with CS/DS/SS behaving as a "pure small model" where "ds=es=ss" (General, ch. 4). The jpi calling convention (register parameter passing) is mandatory for SIBO builds.

Because there is no 8087, "all floating point is performed by software emulation of the 8087" (General, ch. 4).


2. CLIB vs PLIB

Two C libraries are available:

CLIB — "a version of the TopSpeed C library for the EPOC operating system ... a version of the standard ANSI C library." Benefits: portability (existing C ports easily) and less to learn. Documented in the TopSpeed C Library Reference, with SIBO caveats in Notes on CLIB.

PLIB — Psion's proprietary C library, providing "only very thin shells over functionality that is present in the ROM." Functions are prefixed p_.

The manual "strongly urges" developers to consider PLIB over CLIB:

  • "many of the ROM-based EPOC system services are not available from CLIB (eg asynchronous I/O, inter-process messaging, the window server graphics functions)"
  • "executables are larger in CLIB and the process takes a larger data segment."

CLIB is a "much 'thicker' library"; its subsystems "typically require large static buffers and tables." You can freely mix PLIB and CLIB calls (unless using the object-oriented UI dynamic libraries). The Window Server library is WLIB (wlib.lib, functions g.../w..., header wlib.h), automatically linked when needed.

CLIB omissions worth knowing (Notes on CLIB): many BIOS/DOS/graphics/far-pointer functions are not implemented. getRealHandle(int handle) converts a CLIB file handle to a PLIB handle. CLIB startup auto-opens a console channel assigned to stdin/stdout/stderr; you can suppress it by defining p_xwind and setting the global _winHandle.

Standard SIBO C types

The manual's example code uses a standard set of SIBO C types in place of raw C types. (These are defined in the SDK headers — plib.h/stddefs; the General manual uses them but their formal definitions live in the PLIB Reference.) Observed usage:

Type Meaning (from usage in the manual)
TEXT character / text byte (e.g. TEXT *pb;, TEXT subname[P_FNAMESIZE];)
UBYTE unsigned 8-bit byte
WORD signed 16-bit word
UWORD unsigned 16-bit word (e.g. UWORD answer;)
INT signed integer (return type of main)
UINT unsigned integer (e.g. UINT lcdtype;)
VOID void (e.g. main(VOID))

Uncertainty. The exact widths/signedness of each typedef are not spelled out in the General manual; they are defined in the SDK headers / PLIB Reference. The mappings above reflect how the manual uses them and standard SIBO conventions. Use the uppercase SIBO types rather than raw C types in SIBO code.

Storage-class macros

The example code uses uppercase storage-class macros instead of bare C storage classes. These make the code portable across the segmented model and the SDK's linkage conventions.

Macro Used for Manual examples
GLDEF_C global definition of code (a function you define, visible externally) GLDEF_C INT main(VOID), GLDEF_C VOID main(VOID)
GLREF_C global reference to code / an external symbol (declared elsewhere) GLREF_C TEXT *DatCommandPtr;
GLDEF_D global definition of data (data counterpart of GLDEF_C)
GLREF_D global reference to data defined in another module GLREF_D TEXT *DatCommandPtr;
LOCAL_C file-local (static) function LOCAL_C VOID QueueKey(VOID), LOCAL_C VOID CancelTimer(VOID)
LOCAL_D file-local (static) data (data counterpart of LOCAL_C)

_C suffix = code/function, _D suffix = data; GL... = global (external linkage), LOCAL_... = module-private. The General manual shows GLDEF_C, GLREF_C, GLREF_D, and LOCAL_C directly; GLDEF_D/LOCAL_D are the data counterparts (formal definitions in the PLIB Reference).

Program entry point

A PLIB program's entry point is main, written with the SIBO macros:

#include <plib.h>

GLDEF_C INT main(VOID)
{
    p_printf("Hello World");
    p_getch();
    return (0);
}

A CLIB program uses ordinary ANSI style with <stdio.h>:

#include <stdio.h>

int main(VOID)
{
    printf("Hello World");
    getchar();
    return (0);
}

(Both hello.c and p_hello.c are taken verbatim from General ch. 3.)


3. Project (.pr) files

The compile/link cycle is driven by TopSpeed project files (extension .pr). The minimal CLIB project (hello.pr):

#system epoc img
#model small jpi

#compile hello
#link hello

The minimal PLIB project (p_hello.pr):

#system epoc img
#set epocinit=iplib
#model small jpi

#compile p_hello
#link p_hello

Directives

  • #system epoc img — "The end outcome of the build is a .img file, as defined in the Epoc-customised part of the TopSpeed configuration (alternative #systems include dos and win)." Required in every SIBO project file.

  • #model small jpi — small code model + jpi register calling convention (see §1). Required in every SIBO project file.

  • #set epocinit=iplib — selects the startup object and stack size. Must precede #model. It "specifies whether you are using the CLIB or PLIB startup object files, and it specifies the stack size." Allowed values:

    Value Startup Stack
    iclib CLIB 8k (recommended for CLIB)
    iclib4 CLIB 4k
    iclib2 CLIB 2k
    iplib PLIB 4k (recommended for PLIB)
    iplib8 PLIB 8k
    iplib2 PLIB 2k

    If unset, defaults to iclib. You must use CLIB startup if you use any CLIB I/O functions; if you use no CLIB functions at all, "you should always use the PLIB startup." (Non-I/O CLIB functions such as memory allocation can be used with PLIB startup.)

  • #compile <name> — compile a source module. If ambiguous, add the .c extension (e.g. #compile query.c), because the system also treats .a (assembler) and .rc files as candidate sources and errors if two candidates exist.

  • #link <name> — link everything. Its effect: link all #compiled files, plus the startup module and standard libraries, plus anything named by #pragma link, giving the executable the name in the #link statement. The #link name need not match the .pr filename nor any module name.

  • #dolink <name.lib> — used instead of #link to build a library. It stops the system linking in a startup object and standard libraries. The explicit .lib extension overrides the default .img implied by #system epoc img (see §4).

  • #pragma link (<file>) — link an extra object file or library not searched automatically. Examples: #pragma link (hwif.lib) (HWIF is not auto-searched), #pragma link (utils.lib) (a custom library, found along the ts.red search path).

  • #pragma debug (vid=>full) — insert before any #compile to build with full source-level debug info (equivalent to the /v2 command-line flag).

Overriding image attributes

Three project variables (also settable as /s... on the command line):

Variable Meaning Default
%version image version number 0x100f
%priority initial process priority 0x80
%heapsize initial and minimum heap, in paragraphs (0x80 = 0x800 bytes = 2 KB) 0x80

Set via a line like #set heapsize=0x180 or tsc /m app /sheapsize=0x180. The OS refuses to start an instance without heapsize free heap, and never shrinks the heap below it.

Multi-module and parameterised projects

Multi-file program (triple.c, utils1.c, utils2.c):

#system epoc img
#set epocinit=iplib
#model small jpi

#compile triple
#compile utils1
#compile utils2

#link triple

Assembler modules are allowed alongside C (#compile afile assembles afile.a); assembler modules must follow the rules in the PLIB Reference.

A reusable generic project uses the %main macro (unnamed.pr):

#system epoc img
#set epocinit=iplib
#model small jpi
#compile %main
#link %main

invoked as tsc /m unnamed.pr /smain=%1 (TopSpeed /s sets the macro from a batch parameter). Conditional/parameterised builds are handled in the shipped batch files (cc.bat, make.bat, checkvid.bat, vid.bat), which key compilation off a %jpivid% env var (v2 = full debug, v0 = none) and pick a per-app .pr if one exists, else unnamed.pr.

Invoking the compiler: tsc /m

Build and link in one step:

tsc /m hello

"/m ... is that the project file is executed in 'make' mode, with files not being recompiled or relinked needlessly." For source-level debugging add /v2:

tsc /m hello /v2

Other flags seen: /fp<project> selects the project file (e.g. tsc app.c /fpunnamed); with no /m (and no /l) the project runs in "compile" mode (compile only, always, no link). %version, %priority, %heapsize, %main are passed as /s<name>=<value>.

tsc vs tscx. tsc does not use expanded memory "and may cause problems, particularly when linking large applications." If linking fails, replace tsc with tscx (which uses expanded memory) in the batch files.

Configuration files behind the build

The SIBO build config lives in two files (do not edit their pragmas):

  • tsprj.txt — "compiled" with tscfg before use; the SDK version extends Clarion's to add the Epoc img system type.
  • stdepoc.h — "always the first include file in any source module."

Header/library search paths come from the redirection file ts.red (maps *.H, *.LIB, *.PR, etc. to . then the \sibosdk\... directories).


4. Build outputs: images, libraries, device drivers

The image file is produced from an intermediary .exe by the Psion tool emake.exe (automated by the project system). "Essentially, .img files are to Epoc what .exe files are to MS-DOS." emake runs (per tsprj.txt):

emake -b %afl% -o%name% -s -v%version% -p%priority% -h%heapsize% -%epoctype% %name%.exe

The %epoctype variable selects the output kind:

%epoctype Output Extension
t1 (default) image file .img
t2 logical device driver .ldd
t3 physical device driver .pdd
t4 dynamic library .dyl

Images (.img / .app)

.img and .app are "strictly speaking ... no real difference"; both are image files. By convention a .app has one to four embedded add-files:

  • .pic — icon
  • .rsc or .rzc (compressed) — resource file
  • .shd — shell data (Series 3 only)

Add-files are embedded by emake when an add-file list (.afl, a text file naming one to four files, e.g. tele.afl) with the matching base name exists at build time. Renaming the result to .app is a convention (not automatic). A .dfl file similarly embeds DYL files.

Inspect an image with edump <name> (shows version, code/data segment sizes, stack, heap, priority, checksums, add-file offsets, DYL table). Re-edit an existing image's add-files/priority/heap/version without rebuilding using eremake.exe (e.g. to swap in a French resource file):

eremake -afrquery -o..\french\query.app query.app

Libraries (.lib)

Build a static library with #dolink (see §3):

#system epoc img
#set epocinit=iplib
#model small jpi
#compile utils1
#compile utils2
#dolink utils.lib

#dolink (not #link) prevents startup/standard-library linking, and the explicit .lib overrides the default .img. Consumers pull it in with #pragma link (utils.lib).

Loadable device drivers (.LDD / .PDD)

At a high level, LDDs and PDDs are just image-type outputs of emake (%epoctype=t2.ldd, t3.pdd). Detail from the Hardware Development Kit (§9):

All SIBO hardware "is controlled by logical and physical device drivers." Layering:

    APPLICATION SOFTWARE
    Psion C / PLIB call interface
    LOGICAL DEVICE DRIVER   (LDD)
    PHYSICAL DEVICE DRIVER  (PDD)
    PHYSICAL HARDWARE
  • A PDD "contains the code required for talking directly with the hardware" — low-level, hardware-specific services.
  • An LDD "performs the logical processing that transforms these low level services into the high level services used by an application."
  • The same LDD is often paired with a per-hardware PDD; an LDD may also talk to hardware directly, making a separate PDD unnecessary.

Language note. "Psion device drivers are written in 8086 assembler and follow a prescribed structure" (HDK §9). Device drivers are not ordinary C programs — the C SDK / PLIB is the client side (p_loadldd(), p_open(), p_close(), p_iow()). The .ldd/.pdd build path exists in the project system, but the driver source is assembler.

Naming and channels. An LDD name is three characters + colon (e.g. TTY: serial). A PDD name is the owning LDD's three chars + . + three more + colon (e.g. TTY.UAR, the ASIC5 UART driver). Open a channel via p_open:

p_open(&pcb, "LED:", -1);     /* LDD; -1 = ignore open mode */
p_open(&pcb, "TTY.UAR:", -1); /* a PDD directly (unusual) */
p_open(&pcb, "PAR:A", -1);    /* channel qualifier 'A' */

Typically an application opens only the LDD, which opens its PDD during initialisation. I/O requests reach the driver's strategy vector, which maps to the PLIB p_iow() call. Installable drivers are loaded dynamically (DevLoadLDD service; do not call DevInstall directly) without resetting the machine, and can replace resident ROM drivers of the same name (the table is searched from the most-recently-installed end). EPOC handles "a maximum of 32 device drivers on a Series3 machine and 48 on other machines."

LDD structure (HDK §9). Single code segment, no data segments (wrapped by CodeSeg/EndCodeSeg); begins with a LibEnt structure (2-byte LDDSignature, 8-byte zero-terminated name without trailing colon, 2-byte vector count ≥ 8, then the vector table). The eight mandatory LDD functions, in order:

  1. DevFuncInstall — on installation
  2. DevFuncRemove — on removal
  3. DevFuncHold — temporarily disable
  4. DevFuncResume — re-enable
  5. DevFuncReset — application terminated without closing channel
  6. DevFuncUnits — query number of supported units/channels
  7. DevFuncOpen — open a channel
  8. DevFuncStrategy — access functionality via the I/O system

All are called FAR by the OS and must return with a FAR return.

PDD structure (HDK §9). Also single code segment, no data segments; starts with a LibEnt (PDDSignature). Two mandatory functionsDevFuncInstallPDD and DevFuncRemovePDD — with typically two more (DevFuncOpenPDD, DevFuncStrategyPDD). PDD internal variables must live in the driver's own code segment so hardware can be freed after a client process terminates.


5. Floating-point emulator (relevant to any build)

Because SIBO has no 8087, floating point is emulated. Under EPOC the emulator is an LDD, SYS$8087.LDD (in \sibosdk\lib\), loaded and freed automatically — saving ~8K per program and allowing sharing between processes. The C startup (r_emul.a) looks for the LDD in the program's directory, then via the EMS environment variable (case-sensitive). A program that dies with panic 80 before starting has "almost certainly failed to locate SYS$8087.LDD"; fixes: copy the LDD next to the program, set EMS, or remove the floating-point dependency. Note "floating point instructions can easily be generated unexpectedly ... if the recommended build configuration pragmas are 'improved'" — another reason not to touch the pragmas.


6. Resources / resource compiler

The General manual covers resources only lightly, as add-files: a .rsc (or compressed .rzc) resource file can be embedded into a .app via the .afl add-file list (§4). Multi-lingual applications isolate all text in a resource file so a translated .rzc can be swapped in with eremake without recompiling.

Not in this manual. The resource compiler itself and the resource source (.rss) syntax are not described in the General Programming Manual. The manual points to the Resource Files chapter of the Additional System Information manual (not provided here) for that detail. Do not assume resource compiler behaviour beyond the add-file mechanism described above.


7. Detecting the machine at runtime

Most SIBO machines are distinguished by screen size via p_getlcd() / p_geticda() (General ch. 7). Values include:

Constant Value Display / machine
E_LCD_640_400 0 640x400 — MC 400
E_LCD_640_200_SMALL 1 640x200 — MC 200
E_LCD_160_80 4 160x80 — HC
E_LCD_240_80 5 240x80 — Series 3
E_LCD_480_160 11 480x160 — Series 3a / 3c
E_LCD_240_100 12 240x100 — Workabout
E_LCD_240_160 14 240x160 — Siena

Workabout / MX. The manual lists the Workabout as the 240x100 machine (E_LCD_240_100, value 12). It does not list a separate MX entry; an MX with the same screen reports the same LCD type. Series 3a vs 3c (same screen size) are told apart with p_returnexpansionportinfo() on EPOC ≥ 0x390F — the pattern generalises if you must disambiguate same-screen models. Do not hardcode screen dimensions; query the LCD type.


Quick reference

# Minimal PLIB app
#system epoc img          # -> .img output (Epoc image)
#set epocinit=iplib       # PLIB startup, 4k stack  (MUST precede #model)
#model small jpi          # 64K code + 64K data, register calling convention
#compile myapp
#link myapp

# Build (make mode)             tsc /m myapp
# Build with source debug       tsc /m myapp /v2
# Large link needs XMS          tscx /m myapp
# Override heap                  tsc /m myapp /sheapsize=0x180
# Inspect image                 edump myapp
# Re-embed add-files/version     eremake ...
Extension What it is
.pr TopSpeed project file
.c / .a C source / assembler source
.img / .app EPOC image / image with embedded add-files
.lib static library (#dolink)
.ldd / .pdd logical / physical device driver (assembler source)
.dyl dynamic library
.rsc / .rzc resource file / compressed resource file
.pic, .shd, .afl icon, shell data, add-file list
ts.red header/library redirection (search paths)