23 KiB
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 |
|---|---|
| 0–80, and 255 | PLIB library |
| 81–129 | Window Server library |
| 130–160 | OLIB object library |
| 160–254 | 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) |
_Csuffix = code/function,_Dsuffix = data;GL...= global (external linkage),LOCAL_...= module-private. The General manual showsGLDEF_C,GLREF_C,GLREF_D, andLOCAL_Cdirectly;GLDEF_D/LOCAL_Dare 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.imgfile, as defined in the Epoc-customised part of the TopSpeed configuration (alternative#systems includedosandwin)." 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 iclibCLIB 8k (recommended for CLIB) iclib4CLIB 4k iclib2CLIB 2k iplibPLIB 4k (recommended for PLIB) iplib8PLIB 8k iplib2PLIB 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.cextension (e.g.#compile query.c), because the system also treats.a(assembler) and.rcfiles 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#linkstatement. The#linkname need not match the.prfilename nor any module name. -
#dolink <name.lib>— used instead of#linkto build a library. It stops the system linking in a startup object and standard libraries. The explicit.libextension overrides the default.imgimplied 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 thets.redsearch path). -
#pragma debug (vid=>full)— insert before any#compileto build with full source-level debug info (equivalent to the/v2command-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>.
tscvstscx.tscdoes not use expanded memory "and may cause problems, particularly when linking large applications." If linking fails, replacetscwithtscx(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" withtscfgbefore use; the SDK version extends Clarion's to add theEpoc imgsystem 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.rscor.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/.pddbuild 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:
DevFuncInstall— on installationDevFuncRemove— on removalDevFuncHold— temporarily disableDevFuncResume— re-enableDevFuncReset— application terminated without closing channelDevFuncUnits— query number of supported units/channelsDevFuncOpen— open a channelDevFuncStrategy— 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 functions — DevFuncInstallPDD
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 withp_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) |