Twelve new words, stack-underflow checks on every word that takes arguments,
and fixes for banked memory. Tested on real MicroBeast hardware.
New words
Stack
NIP ( a b -- b ), TUCK ( a b -- b a b ) (Core Extensions)
-ROT ( a b c -- c a b ), the reverse of ROT (antforth extension)
? ( addr -- ) prints the value stored at an address, the same as @ . (Tools)
Comparisons
<>, 0<>, 0>, U> (Core Extensions)
<=, >=, signed (antforth extensions)
Loops
BEGIN … AGAIN for loops that never end on their own, such as task bodies
and event loops. Leave with EXIT.
?DO runs zero times when the limit equals the start (DO would run 65,536
times). It works with LOOP, +LOOP, LEAVE, UNLOOP, I and J.
Core Extensions coverage goes from 15 to 23 of the 46 words.
Stack underflow is caught everywhere
Every kernel word and assembler mnemonic that takes stack arguments now raises -4 stack underflow when they're missing. The check runs before the word
changes anything. Before, DUP, 0=, 1+, TYPE, FILL, MOVE, ACTIVATE and about 45 others silently worked on whatever was left in a
register.
PICK and ROLL check that the stack holds u items.
SET-ORDER and RESTORE-INPUT count their arguments correctly.
An assembler instruction missing an operand raises -4 before it writes any
code bytes, so a caught error leaves HERE where it was.
Fixes
>NUMBER swapped the two halves of its starting value. With a nonzero
start, 1 0 S" 0" >NUMBER 2DROP D. printed 655360 instead of 10.
Starting from 0 0, as most code does, wasn't affected.
Banked words could hide or hang kernel words. Giving +BANK the same
physical page twice made two banks share one piece of RAM, so defining words
in one bank overwrote dictionary entries in the other. A lookup could then
miss CR or BANK!, or hang. +BANK now refuses a page that is already
listed (dup?, error -2), and so does the boot page list.
Parsing near the top of a bank could write into fixed RAM. When a bank
above 0 was nearly full, the parse buffer and PAD ran past $C000 and
overwrote bank 0's dictionary. Banks above 0 now end at $BF58, so .BANKS
shows 16,216 bytes free per bank instead of 16,384.
WORD refuses words over 128 characters with error -18, so even EVALUATE can't overflow the parse buffer.
Things to know when upgrading
Room for only two tasks. Every task's control block (TCB) must sit below $8000, and this kernel leaves room for two named tasks
(TASK CONSTANT name), with 25 bytes to spare. A third TASK raises -8.
Create your tasks before anything else. TRAFFIC.FTH now does this, so LIGHTS is task 1. To see the free room, type DECIMAL 32768 HERE - . at a
fresh prompt. See "How many tasks fit" in docs/phase6-multitasker.md.
Scripts that add the same page twice ($22 +BANK $22 +BANK) now fail.
Use a different page for each bank.
The underflow checks cost some speed: INCLUDE is about 10–14% slower in
CPU cycles. Disk I/O is unchanged, so the real-world difference is smaller.
Banked code must leave 168 bytes below $C000. Code that filled a bank to
the brim before now raises -8 sooner.
Size
antforth.com is 31,395 bytes, 641 more than v3.2.1.
AntForth 3.3.0 released
I made a new release of AntForth, because there are a couple of words I need for another project. There were a few digressions and bugfixes along the way!
Get it here.
Release notes:
antforth v3.3.0
Twelve new words, stack-underflow checks on every word that takes arguments,
and
The new Feersum Technology NanoBeast is nearly with us! It shares a lot of DNA with the MicroBeast, but there are some subtle differences that might call for special-case handling in any generic cross-beast software. How do we go about that?
Examining the firmware
The easiest approach is to look at the device_idlocation in the firmware. This will return 0x1 if you're currently running on a MicroBeast, and 0x2 if you're running on a NanoBeast, as per these defined constants.
The downside to this approach is that this magic memory location isn't at a fixed address: the next firmware released could move it to a different address. And it doesn't exist at all for firmware versions prior to 1.8.
This means that to be truly portable we'd have to detect the firmware version first, and then detect the hardware platform, which is a tad irksome, but not insurmountable.
What we need is a more portable approach.
Detecting the hardware
We can start by taking a look at how the firmware does it: what's writing 0x1 or 0x2 into the device_id location?
This code is writing values to the PIO_A_CTRL and PIO_B_CTRL IO ports. These ports map to the Z84C20 PIO controller chip. This is the chip that provides the two 8-bit bidirectional I/O ports (with handshaking) that's used on the MicroBeast.
And that's the crucial part: the NanoBeast does not use the same chip, it has custom PIO logic in its own CPLD IC.
Here are the PIO-related IO ports for both devices, side by side:
IO address
MicroBeast
NanoBeast
0x10
Port A data
Port A data
0x11
Port B data
Port A direction
0x12
Port A control
LCD Translate (low)
0x13
Port B control
LCD Translate (high)
0x14
Port B data
0x15
Audio
0x16
Interrupt control
0x17
I2C data
So we can see that on a NanoBeast, ports 0x12 and 0x13 are no longer PIO_A_CTRL and PIO_B_CTRL but rather something new and mysterious called LCD Translate. I surmise this is glue logic in the NanoBeast's CPLD to support the NanoExplorer peripheral, which has a single line LCD display.
Crucially, these registers have an ID function, in that if we write the value NIO_TEST_BITS to both LCD control registers, the lower one will respond with NIO_VALID_LOWER and the upper one with NIO_VALID_UPPER.
If we pull the same trick on a MicroBeast we will be writing to PIO_A_CTRL and PIO_B_CTRL. The value chosen (NO_TEST_BITS, 0x4f) has the effect of setting a PIO port into input mode (Mode 1), so it will affect the MicroBeast's PIO ports, but does so in an electrically safe way.
When we attempt to read back PIO_A_CTRL and PIO_B_CTRL on the MicroBeast we will get nonsense, because those registers are write-only in a real PIO chip.
Tidying up after ourselves
The BeastOS firmware does hardware detection before it sets up the PIO ports to support system functionality. If we emulate how it works in our own code, we'll have clobbered the system PIO port configuration on a MicroBeast. Therefore it is crucial to restore the ports to a known, working state on the MicroBeast, otherwise we might find that the Real Time Clock, I2C, and UART no longer function.
Unfortunately it's not as simple as reading the incumbent values of PIO_A_CTRL and PIO_B_CTRL before we start messing about and restoring them afterwards: remember that real PIO control registers are write-only.
Here's what it looks like in Forth, the World's Greatest Language:
( microbeast ports)
0x10 constant M_A_DATA
0x12 constant M_A_CTRL
0x11 constant M_B_DATA
0x13 constant M_B_CTRL
( nanobeast ports)
0x10 constant N_A_DATA
0x11 constant N_A_DIR
0x14 constant N_B_DATA
( nanobeast detection)
0x4f constant NIO_TEST_BITS
0xd4 constant NIO_VALID_LOWER
0xfa constant NIO_VALID_UPPER
0xcf constant M_B_MODE
0xff constant M_B_IOMASK
: is-nanobeast
NIO_TEST_BITS M_A_CTRL OUT
NIO_TEST_BITS M_B_CTRL OUT
M_A_CTRL IN NIO_VALID_LOWER =
M_B_CTRL IN NIO_VALID_UPPER =
and
( restore state)
M_B_MODE M_B_CTRL OUT
M_B_IOMASK M_B_CTRL OUT
;
: wotbeast
is-nanobeast IF ." nanobeast" ELSE ." microbeast" THEN CR ;
is-nanobeast is a new word that does the actual checking, and wotbeast uses it to print a human-readable identifier.
And here's that code running on a NanoBeast and a MicroBeast, side by side:
If you'd like to watch a video of me doing this from scratch then you're in luck:
Future directions
The 1.8 firmware's built-in device_id facility is supremely useful, as it is a cached version of the above hardware check, done at the right time (i.e. before the PIO ports got set up by the system for its own use).
If we can use that instead of probing the hardware ourselves then we don't perturb the hardware and we don't have to restore it afterwards.
But the forwards and backwards compatibility remains a problem. For this reason, it is highly likely that the NanoBeast release firmware will include the same functionality in a more portable form: possibly as part of the existing MBB_GET_VERSION BIOS call.
Sexing your Beasts
The new Feersum Technology NanoBeast is nearly with us! It shares a lot of DNA with the MicroBeast, but there are some subtle differences that might call for special-case handling in any generic cross-beast software. How do we go about that?
Examining the firmware
The easiest approach is to look
This version of SLIDE brings it up to date with the latest version of the SLIDE Protocol spec. The headline item is the introduction of a "command channel" which allows your main computer to to send simple commands to the MicroBeast and get responses back, during a file transfer session. Currently those commands are:
VOLS - what volumes (drives) are present on the 'Beast
DIR - what files are in a particular volume?
REN - rename a file
DEL - delete a file
I know what you're thinking: all MicroBeasts have two drives but that might not always be the case!
These commands are mainly intended for use by impending BeasTTY features, but they're available right now in the command line helper tools:
SLIDE — command-channel probe (wire v0.3 §2)
Port: /dev/ttyUSB4 @ 19200 baud
Probe: ENQ 0x05, 3 attempt(s), 500ms echo timeout
Typing slide r at the CP/M prompt...
✓ Z80 connected. (46 stray byte(s) skipped, wakeup signature seen)
--- Probe result (after handshake) ---
Outcome: supported
Attempts: 1
Queued before probe: (none)
Stray bytes during probe window: (none)
VER raw: 30 30 30 32 ('0002')
Version: major 0, minor 2
We speak: major 0, minor 2
--- CMD_REN (current drive SLIDE.COM -> SLIDE2.COM) ---
Status: 0x00 — ST_OK
(no records — probe-only exchange completed)
--- FIN exchange (session-intact check) ---
FIN echoed. The peer was still in its file loop and exited
cleanly — the probe did not disturb the session.
VERDICT: probe outcome 'supported', session intact.
DEL
./slide probe /dev/ttyUSB4 --start-cmd "slide2 r" --cmd del --match=RESET.COM
gets you:
SLIDE — command-channel probe (wire v0.3 §2)
Port: /dev/ttyUSB4 @ 19200 baud
Probe: ENQ 0x05, 3 attempt(s), 500ms echo timeout
Typing slide2 r at the CP/M prompt...
✓ Z80 connected. (47 stray byte(s) skipped, wakeup signature seen)
--- Probe result (after handshake) ---
Outcome: supported
Attempts: 1
Queued before probe: (none)
Stray bytes during probe window: (none)
VER raw: 30 30 30 32 ('0002')
Version: major 0, minor 2
We speak: major 0, minor 2
--- CMD_DEL (current drive RESET.COM) ---
Status: 0x00 — ST_OK
(no records — probe-only exchange completed)
--- FIN exchange (session-intact check) ---
FIN echoed. The peer was still in its file loop and exited
cleanly — the probe did not disturb the session.
VERDICT: probe outcome 'supported', session intact.
As you can see, it's a little verbose: there's a lot of diagnostic information in there that we used during backwards compatibility testing.
We did a lot of backwards compatibility testing...but there are always gremlins lurking. If you're feeling helpful please adopt slide v0.6.1 as your daily driver and give it a whirl while we wait for the new BeasTTY features - let me know of any issues!
SLIDE v0.6.1 released
I just released version 0.6.1 of SLIDE, the PC<-->Beast serial file transfer tool.
This version of SLIDE brings it up to date with the latest version of the SLIDE Protocol spec. The headline item is the introduction of a "command channel" which
The NanoBeast has a different chip that we'll cover another time, but it is broadly compatible [1].
I think - not had a chance to play with one yet. ↩︎
We can treat the Z84C20 as a container for two ports which are (almost) identical. Each port has some registers that allow us to control it: that's a specific set of addresses in the Z80 CPU's I/O address space that allow us to control the Z84C20. These are:
a CONTROL register, that configure the port, including setting its mode, which pins are inputs and which outputs, and which pins generate an interrupt on change.
a DATA register that lets us read or write the 8 signals that this port owns.
Port modes
Each port supports 4 different modes, they are:
mode 0: output a byte at a time
mode 1: input a byte at a time
mode 2: input/output a byte at a team (steals some signals from Port B, so this is only available on Port A)
mode 3: bit control mode
We'll be using mode 3, as we want individual control of 3 different LEDs, and this mode is the easiest way to achieve it.
Traffic light circuit
Let's get started on the hardware! First of all we're going to need a bit of breadboard, some LEDs and resistors, lots of precut wire links / bits of wire, and some hookup wires with 0.1" female headers for connection to the MicroBeast. We'll also need an integrated circuit, a 74HC244.
Here's an overview of what we'll be building:
First off we'll need some LEDs of the appropriate colours. I raided the parts drawer and came up with three likely candidates of dubious provenance. Measuring them with a multimeter I found the voltage drop across my red LED was 1.7v, the yellow LED was 1.8v, and the green LED was 2 volts.
We want to limit the current through these LEDs to about 10mA (plenty bright enoufg for our purposes), so we'll need a resistor for each LED. We can calculate the resistor value with the formula R = (Vcc - Vf) / If where Vf is about 2 volts (as measured), Vcc is 5V and If is 10 mA. This gives a value of 300 ohms. I don't have any 300 ohm resistors, but I do have 330 Ω, and pllugging that back in to the formula reveals that that gives an If of 9 mA, which is absolutely fine.
You might be wondering why we don't wire the LEDs directly to the 'Beast's PIO output: the Z84C20 can neither source (provide) nor sink (accept) 9mA - in fact it can only source 1.6 mA and sink 2mA, which is rather puny.
💡
These values are labelled IOL and IOH in the z84c20 datasheet, and you have to really search for them. They're hidden in the right-hand "Test Conditions" column of the "DC Characteristics" table in the rows for VOL and VOH.
This is the reason for the 74HC244 IC in our circuit.
74HC244 3-state octal buffer and line driver
This is a package of 8 (hence "octal") "buffer"s, where a buffer is a device that has one input and one output, and the output's value follows the input. This might sound a bit pointless, but the buffer also isolates the input from the output, and provides a significant current boost.
In the case of 74HC2444, each pin can sink or source 35mA, with the proviso that the total current handled by the chip cannot exceed 70mA. So our 3 * 9 mA = 27 mA total is not going to cause the 74HC244 to break a sweat, even though it would give the Z84C20 a bit of a migraine.
Just to be safe, we're using the 74HC244 to sink current rather than sourcing it: this means that current flows from the power supply, through the LED and resistor, and is delivered into the 74HC244. This has the consequence of inverting the control logic (we drive a line LOW to light the LED) - more on this later.
You can buy 74HC244s from all the usual component suppliers, but they are often expensive in small quantities. eBay or AliExpress are a much cheaper option if you can wait for a few days, but beware fakes and duds. I bought 10 from AliExpress and they all checked out fine. If you're really desperate, get hold of me on Discord and I'll send you one.
💡
When you're wiring up the 74HC244, you'll notice that there are a lot of wire links connecting the chips unused inputs to ground. DO NOT SKIP these, otherwise you'll get all sorts of weird random flickery behaviour! Ask me how I know!
Our RED led is wired to the 'Beast's PA0 pin, the amber LED to PA1, and the green PIN to PA2:
This means writing the value 0x01 to Port A's data port will control the red LED, the value 0x02 will control the amber LED, and the value 0x04 will control the green LED.
The rubber band is there to provide "strain relief", i.e. stop the Dupont pins pulling out of the board when you move the cables.
Don't forget to connect your power rails together (as in, connect the top + rail to the bottom + rail, and connect the top - rail to the bottom - rail) – do not connect a + rail to a - rail! In the Fritzing diagram this is shown as a long pink wire and a long black wire across the centre of the board. In the photo above, it's the two X shaped grey wires at the bottom.
Also worth noting that your LEDs, being diodes, need to go in the right way round. If you fit them in reverse, they won't work. An LEDs terminals are called the "cathode" and the "anode". Current must enter the LED at the anode and leave via the cathode. The cathode is usually marked by a slight flattening on the LED housing, and it usually has a shorted lead than the anode. If in doubt, test with a multimeter first.
I'll leave you to go have fun building the circuit. In part 2, we'll write some simple code to drive it!
Build a traffic light!
The MicroBeast and NanoBeast are extremely expandable computers - they offer a range of interfaces including GPIO, I2C, UART, and RC2014. These interfaces vary in their complexity and capabilities, ranging from turning a simple LED on and off all the way up to a full-blown graphics card.
We'll
This is a pity, because the VT52 supports a number of control codes that allow it to do exciting things like move the cursor, clear the screen, enter graphics mode and, .... well that's about it really, but you can achieve a lot with those, like:
0:00
/2:41
The rudimentary game above uses VT52 control codes to move the cursor so that it can draw the bat, the ball and the score in their correct places. It's not blazingly fast, and it's a bit flickery, but it is playable!
The terminal that's being used to play that game is BeasTTY.
it runs in the web browser, so nothing needs installing
it has SLIDE support built in, for drag-and-drop file transfer (in both directions)
it comes with a number of "8 bit" fonts, including the font from the original VT52 ROMs.
How to run BeasTTY
The simplest way is to click on this link which also appears top-right on the github page.
Then click on the "Connect" button at the top, and choose your 'Beast in the resulting dialogue box:
Click on the big square in the middle (so the border goes solid) and hit ENTER, and you should be rewarded with your CP/M prompt:
Type some commands to get more output:
This is the "Amber CRT" display option with the original VT52 font. It looks its authentic best in white:
You can change the CRT colour (or switch to a modern "Clean" look) using the controls at the top of the screen:
If you want to change the font, open the "Settings" section underneath the main text window, and choose a font from the "CRT font" drop-down:
The fonts only apply in CRT mode: in "clean" mode you get a built-in monospaced font. Also note that the VT52 ROM font is the only one that supports the custom VT52 alternate graphics set (although I wouldn't get too excited, it's a pretty poor choice).
How to send a file from your PC
There are a couple of ways to send a file from your PC to your 'Beast using BeasTTY.
Drag and drop
Just find the file you want to send in your file manager, and drag it onto the central region of the BeasTTY window (make sure your 'Beast is currently logged into a writable drive (i.e. B: not A:):
0:00
/0:15
Notice the "chip" top right that shows you the progress: it also gives you the option to cancel the transfer.
This works because when you drag a file into the window, BeasTTY first sends the command "B:SLIDE R\r" to your 'Beast, which executes B:SLIDE.COM R which runs the SLIDE executable in receive mode.
So for this to work, you're going to need SLIDE.COM on your B drive. You can get it from the SLIDE repo.
Note that versions of the MicroBeast firmware from 1.7 onwards include the SLIDE.COM binary on the A disk as standard. In this case, you need to tell BeasTTY that you want to run the A drive version - you can do this by expanding "Settings", then expanding the "SLIDE file transfer" section, then changing the "Auto-send command" option: in this case, change it to A:SLIDE R - don't forgot the 'R' at the end ("receive" mode).
Send file button
Another way to send a file is to click the "Send File" button at the top of the screen. This will give you a standard OS file picker where you can choose the file you want to send.
0:00
/0:12
How to send a file from your 'Beast
Sending a file from the 'Beast is initiated from the 'Beast itself. In the terminal window, type A:SLIDE S <name-of-file> or B:SLIDE S <name-of-file> if your SLIDE.COM binary is on the B drive.
The "S" after "SLIDE" means "send".
0:00
/0:19
You can see in my example that the received file is automatically saved to a folder called "INCOMING" on my hard disk. This is achieved by setting the "Save received files to a folder" option and specifying a folder with "Change folder...".
💡
NB BeastTTY remembers your folder choice, but the browser will periodically require that you grant it permissions anew.
If you turn off "Save received files to a folder" then received files will use the standard browser download mechanism.
How to send multiple files from your PC
SLIDE lets you send more than one file at a time!
Drag and drop
Simply drag multiple files onto the BeasTTY window in one go. SLIDE will transfer them all individually.
0:00
/0:44
Send file button
It probably doesn't come as a great surprise to hear that you can select multiple files in the file picker that appears when you click on the "Send file" button:
0:00
/0:23
How to send multiple files from your 'Beast
Type SLIDE S <file1> <file2>... to send multiple files from the 'Beast to BeasTTY with SLIDE (that lone S means "Send"). You can specify as many files as you like, but remember that the whole line can only be 128 characters.
0:00
/0:25
These will end up in your "Save received files to a folder" folder, as for the single file case. If you haven't set this folder, your browser will use the standard download mechanism, but it will likely prompt you first to check that it's OK to download multiple files.
Introducing BeasTTY
You've probably used a terminal emulator like Terraterm or Minicom with your 'Beast countless times, but you might be surprised to learn that it's actually designed to work with a specific terminal: the DEC VT52 circa 1975. Unlike the more well-known VT100, it's
We're going to be building on that foundation here, so if you skipped earlier parts or are still a little shaky on the fundamentals you might want to go back and read them again.
To move forward, I'm going to assume that you've got SLIDE.COM on the B: drive of your 'Beast, and that you have the corresponding PC utility installed somewhere in your PATH on your development PC. Refer to the SLIDE README if you need more help with that.
I'm also going to assume that you have BBCBASIC.COM on your B drive.
We're going to start in the scrolltext/bbc_basic/effects folder.
Background info
This time, we're going to build on our scrolltext program from before, but add a little bling.
In the first installment of this series I pointed you at the LED driver datasheet and just on the off-chance that you didn't pore over this document at the time, let me quote a bit of it here:
What this is telling us is that we can change the brightness of each character on our LED display, and that each character can be set to 256 different brightness levels!
This means we can do some cool fading effects by varying the brightness. In fact you might have noticed something similar when the 'Beast first boots. The fact that the firmware is already doing this might lead you to hope that perhaps there's a BIOS routine we can call to do the heavy lifting for us, and your faith is rewarded:
I've left the familiar MBB_WRITE_LED code on there for reference. This time we're interested in MBB_LED_BRIGHTNESS, and we can see that the address this resolves to (0FDD3h) is different from MBB_WRITE_LED (&FDD6), and moreover this time the column is passed in the A register as before, but this time brightness is passed in the C register. (Note that the comment in the firmware header is wrong; there are actually 256 brightness levels, not 128.)
The rough plan is, we'll send lovely shimmering waves of varying brightness along our text string as it's scrolling. In order to pull this off, we'll construct a lookup table where we've pre-computed brightness levels so that they follow a sine wave pattern, like this:
We could generate this lookup table in BASIC using the SIN() function, but it's a little awkward and frankly a lot easier to generate the values and cast them to integer values in the correct range using a python script on a modern PC, so that's exactly what I did.
Here's the code we'll be running this time:
10 REM === MicroBeast LED Demo - Step 5: Sine Wave Brightness (BBC BASIC) ===
20 REM Scrolling text with a sine-wave brightness effect. The brightness
30 REM wave scrolls independently of (and faster than) the text.
40 REM
50 REM Two BIOS routines are used. BBC BASIC loads the Z80 registers
60 REM from the static integer variables A%-L% before a CALL, so each
70 REM is just a couple of lines - no machine-code stubs required:
80 REM MBB_WRITE_LED (&FDD6): HL = bitmask, A = column
90 REM MBB_LED_BRIGHTNESS (&FDD3): A = column, C = brightness
100 REM
110 REM --- Load font data into an array (ASCII 32-126) ---
120 DIM font%(94)
130 FOR idx% = 0 TO 94 : READ font%(idx%) : NEXT
140 REM
150 REM --- Load sine table (64 entries) ---
160 DIM sine%(63)
170 FOR idx% = 0 TO 63 : READ sine%(idx%) : NEXT
180 REM
190 REM --- Get user input ---
200 INPUT "Enter scroll text: " text$
210 REM
220 REM --- Build padded buffer: 24 spaces + text + 24 spaces ---
230 pad$ = " " : REM 24 spaces
240 buf$ = pad$ + text$ + pad$
250 buflen% = LEN(buf$)
260 REM
270 PRINT "Scrolling with effects... press ESCAPE to stop"
280 offset% = 1 : REM text scroll position (1-based)
290 boff% = 0 : REM brightness wave offset
300 frame% = 0 : REM frame counter
310 REM
320 REPEAT
330 REM --- Paint the 24 visible characters ---
340 FOR col% = 0 TO 23
350 ch% = ASC(MID$(buf$, offset% + col%, 1)) - 32
360 IF ch% < 0 OR ch% > 94 THEN ch% = 0
370 PROCled(font%(ch%), col%)
380 NEXT
390 REM --- Animate the brightness wave 4 times per text step ---
400 REPEAT
410 FOR col% = 0 TO 23
420 PROCbright(sine%((col% + boff%) AND 63), col%)
430 NEXT
440 boff% = (boff% + 1) AND 63
450 frame% = (frame% + 1) AND 3
460 UNTIL frame% = 0
470 REM --- Advance text position, wrapping at the end ---
480 offset% = offset% + 1
490 IF offset% > buflen% - 23 THEN offset% = 1
500 UNTIL FALSE
510 END
520 REM
530 REM --- Write bitmask bm% to LED column col% via MBB_WRITE_LED ---
540 DEF PROCled(bm%, col%)
550 A% = col% : L% = bm% MOD 256 : H% = bm% DIV 256
560 CALL &FDD6
570 ENDPROC
580 REM
590 REM --- Set brightness br% (0-255) of LED column col% via MBB_LED_BRIGHTNESS ---
600 DEF PROCbright(br%, col%)
610 A% = col% : C% = br%
620 CALL &FDD3
630 ENDPROC
640 REM
650 REM --- Font DATA (ASCII 32-126, 95 entries) ---
660 DATA &0000, &4900, &0202, &12CE, &12ED, &2DE4
670 DATA &0B59, &0200, &0C00, &2100, &3FC0, &12C0
680 DATA &2000, &00C0, &4000, &2400
690 DATA &243F, &0406, &00DB, &008F, &00E6, &0869
700 DATA &00FD, &1401, &00FF, &00EF, &0040, &2200
710 DATA &0C40, &00C8, &2180, &5083
720 DATA &02BB, &00F7, &128F, &0039, &120F, &0079
730 DATA &0071, &00BD, &00F6, &1209, &001E, &0C70
740 DATA &0038, &0536, &0936, &003F
750 DATA &00F3, &083F, &08F3, &00ED, &1201, &003E
760 DATA &2430, &2836, &2D00, &00EE, &2409
770 DATA &0039, &0900, &000F, &2800, &0008
780 DATA &0100, &208C, &0878, &00D8, &208E, &2058
790 DATA &14C0, &048E, &1070, &1000, &2210
800 DATA &1E00, &1200, &10D4, &1050, &00DC
810 DATA &0170, &0486, &0050, &0888, &0078
820 DATA &001C, &2010, &2814, &2D00, &028E
830 DATA &2048, &2149, &1200, &0C89, &24C0
840 REM
850 REM --- Sine table (64 entries, values 0-255) ---
860 DATA &0080, &008C, &0098, &00A5, &00B0, &00BC, &00C6, &00D0
870 DATA &00DA, &00E2, &00EA, &00F0, &00F5, &00FA, &00FD, &00FE
880 DATA &00FF, &00FE, &00FD, &00FA, &00F5, &00F0, &00EA, &00E2
890 DATA &00DA, &00D0, &00C6, &00BC, &00B0, &00A5, &0098, &008C
900 DATA &0080, &0073, &0067, &005A, &004F, &0043, &0039, &002F
910 DATA &0025, &001D, &0015, &000F, &000A, &0005, &0002, &0001
920 DATA &0000, &0001, &0002, &0005, &000A, &000F, &0015, &001D
930 DATA &0025, &002F, &0039, &0043, &004F, &005A, &0067, &0073
Now you can see that we've got two machine code procedures (one for displaying characters and one for setting brightness) and also two lookup tables now (one for the "font" and one for our sine-wave of brightness values).
Our character display loop (which starts at line 320) has changed quite a bit. It starts out as before, then at line 680 we have:
390 REM --- Animate the brightness wave 4 times per text step ---
400 REPEAT
410 FOR col% = 0 TO 23
420 PROCbright(sine%((col% + boff%) AND 63), col%)
430 NEXT
This goes through every column again, setting a suitable brightness value.
After that, we have this bit of chicanery:
440 boff% = (boff% + 1) AND 63
450 frame% = (frame% + 1) AND 3
What this is doing is incrementing both the brightness offset and the frame counter. When we increment the brightness offset, we AND 63 - this means that we keep only the bottom 6 bits of FC% so the effect is that whenever its value is 63 and we implement it, it wraps around to zero again.
This kind of modulo arithmetic with numbers that are a power of 2 is very, very common particularly in low level code like C or assembly language. The reason it's so ubiquitous is that a lot of maths operations in base 2 (binary) can easily be implemented with simple (and fast!) logic instructions that execute directly on the processor, like the AND we just saw. The alternative would be to do actual division and find the remainder, which is incredibly slow and tedious on old hardware like the z80. The z80 doesn't have a DIVIDE instruction, you'd have to write your own division routine.
We could of course use division in BASIC, but we'll come to why that's not such a great idea in a moment.
Running the code
This should be second nature by now:
Boot your 'Beast
SLIDE the EFFECTS.BBC file from the repo across to your 'Beast's B drive
"log in" to the B drive with B:
start BBC Basic with BBCBASIC
type LOAD "EFFECTS"
inspect it with LIST
run it with RUN
All being well, you should see this (your string might be different):
One thing you'll notice straight away is that it is monumentally slow. There's not even an artificial delay loop in there that we can tweak - this is running at full tilt! The sad truth is that while high-level languages like BASIC are great for learning how to code and writing simple programs, they squander a lot of the machine's power turning those fancy BASIC statements into machine code that the processor can execute.
To get more performance out of the processor (and believe me, it can go a lot faster!) we'll have to put aside BASIC, and like the bedroom-based game developers of yore teach ourselves z80 assembler..
Things you can try
Can you see any way to make the BASIC code quicker?
Try making the brightness wave go in the opposite direction!
It's not possible for the eye to actually discern 256 levels of brightness: how can you adjust the sine wave table so that the effect is more striking?
End of Part Six
That's it for Part Six, and also for BBC Basic!
In the next part Your name in lights! (Part 7) we'll learn some z80 assembler, by re-implementing the programs we've already written.
If you're not quite ready for that yet and want to experiment a bit further in BASIC then by all means do so: experimentation is the best way to learn!
If you want to dabble in other high-level languages you are spoilt for choice...there are CP/M implementations of Algol, COBOL, Fortran, Pascal, LISP, Forth, and C, and probably many more besides. These are easily found on the web, and most will run on the 'Beast without issue (just make sure it's the z80 + cp/m 2.2 version you're trying to run.
Well done for making it this far - the real fun is about to begin!
microbeast
Your name in lights! (Part 6) (BBC BASIC)
Last time in Your name in lights! (Part 5), we get a basic scrolltext working and were able to scroll any message of our choosing.
We're going to be building on that foundation here, so if you skipped earlier parts or are still a little shaky on the