Wednesday, 29th October 2008
It's been a while since I worked on the TI-83+ calculator port of BBC BASIC, and due to a relatively modular design some of the new features I'd been working on for the Z80 computer project version could be easily transferred across.
The first addition to the calculator port is the TIME$ keyword, which lets you get or set the system time.
That's all very well and good, but only the TI-84+ calculator has real-time clock hardware - the TI-83+ doesn't have any sort of accurate timekeeping to speak of. Rather than display an error when TIME$ is used I opted to use an inaccurate software-based clock. It uses the TI-83+'s timer interrupts (roughly 118Hz) to update the date and time about once a second. The clock is reset to Mon,01 Jan 2001.00:00:00 every time BBC BASIC is restarted and keeps abysmal time, but software designed to use the clock will at least run.
I have been transferring and amending documentation from Richard Russell's website to a private installation of MediaWiki. There are about 120 entries so far; having documentation puts me much closer to being able to make a release.
I have also fixed a handful of bugs. One that had me tearing my hair out was something like this:
250 DEF PROC_someproc(a,b) 260 a=a*PI 270 ENDPROC
The program kept displaying a No such variable error on line 260. Well, a is clearly defined, and retyping the procedure in another program worked, so what was the problem here? I thought that maybe one of the graphics calls or similar was corrupting some important memory or modifying a register it shouldn't. It turns out that the problem lay in the Windows-based tokeniser - it was not picking up PI as a token, for starters, and was storing the ASCII string "PI" instead. On top of that, it was treating anything after a * as a star command, which aren't tokenised either. (Star commands, such as *REFRESH, are passed directly to the host interface or OS). Retyping the problematic lines caused BBC BASIC to retokenise them, which was why I couldn't replicate the problem in other programs. By fixing the tokeniser, everything started working again.
The source code for the analogue clock program is listed below.
10 *REFRESH OFF 20 VDU 29,48;32; 30 GCOL 0,128 40 REPEAT 50 t$=TIME$ 60 hour%=VAL(MID$(t$,17,2))MOD12 70 min%=VAL(MID$(t$,20,2)) 80 sec%=VAL(MID$(t$,23,2)) 90 sec=sec%/60 100 min=(min%+sec)/60 110 hour=(hour%+min)/12 120 CLG 130 GCOL 0,127 140 MOVE 0,0 150 PLOT 153,31,0 160 GCOL 0,0 170 FOR h=1TO12 180 hA= h/6*PI 190 hX=30*SIN(hA) 200 hY=30*COS(hA) 210 MOVE hX,hY 220 DRAW hX*0.9,hY*0.9 230 NEXT h 240 PROC_drawHand(sec,30) 250 PROC_drawHand(min,24) 260 PROC_drawHand(hour,16) 270 *REFRESH 280 UNTIL INKEY(0)<>-1 290 *REFRESH ON 300 END 310 DEF PROC_drawHand(pos,length) 320 MOVE 0,0 330 pos=pos*2*PI 340 DRAW length*SIN(pos),-length*COS(pos) 350 ENDPROC
I translated the tokeniser source code to PHP so that by pointing a browser to file.bbcs for a known file.bbc the highlighted, detokenised source code is served as HTML instead. Hurrah for mod_rewrite, and if you're using IIS Ionic's Isapi Rewrite Filter performs a similar job using the same syntax.
Sunday, 5th October 2008
I've made a few additions to the operating system for the computer. The Console module, which handles text input and output, now supports "coloured" text - that is you can set the text foreground and background colours to either black or white. This functionality is exposed via the BBC BASIC COLOUR statement. If you pass a value between 0 and 127 this sets the foreground colour (0..63 is white, 64..127 is black) and if you pass a value between 128 and 255 this sets the background colour (128..191 is white, 192..255 is black).
The image on the right also demonstrates another addition - you can set the text viewport to occupy a partial area of the display. This is most useful when coupled with the ability to define graphics viewports, which I have yet to add.
That said, I have started writing the Graphics module. So far all it can do is draw clipped lines, and this functionality is exposed via BBC BASIC's MOVE and DRAW statements. MOVE sets the graphics cursor position - DRAW also moves the graphics cursor, but also draws a line between the new position and the previously visited one.
I cannot use drawing code I've written for the TI-83+ version due to differences in the LCD hardware and the way that buffers are laid out. The popular way to lay out graphics buffers on the TI-83+ is as follows:
Each grey block represents 8 pixels - one byte in LCD memory represents 8 pixels grouped horizontally. The leftmost bit in each 8-pixel group is the most significant bit of each byte. The data is stored in the buffer so that each row of the LCD is represented by 12 consecutive bytes. This left-to-right, top-to-bottom arrangement should seem sensible to anyone who has worked with a linear framebuffer. However, due to the way that the LCD I'm using is arranged, I'm using the following buffer layout:
The LCD hardware groups pixels vertically, but when you write a byte to it its internal address pointer moves right. Furthermore, the most significant bit of each byte written is at the bottom of each group. This may sound a little confusing, but actually works out as more efficient. Writing text is easy; I'm using a 4×8 pixel font, so all I need to do is set the LCD's internal address counter correctly then write out four bytes, one for each column of the text (other sensible font sizes for the display, such as 6×8 or 8×8 are just as easy to display).
Another example of improved efficiency is if you deal with pixel-plotting routines. Each pixel on the display can be addressed by a buffer offset and an eight-bit mask to "select" the particular pixel in an eight-pixel group. With this arrangement, moving the pixel left or right is easy; simply increment or decrement the buffer offset by one. Moving the pixel up or down is a case of rotating the mask in the desired direction. If the rotation moves the pixel mask from one 8-pixel group to another (which only happens every eight pixels) the buffer offset needs to be moved by 128 in the correct direction to shunt it up or down.
On the TI-83+, moving the pixel up or down requires moving the buffer offset up or down by 12; moving the pixel left or right is a rotation as before with a simple buffer offset increment or decrement to move between 8-pixel groups.
In Z80 assembly incrementing or decrementing a 16-bit pointer by one is a single instruction taking 6 clock cycles; moving it by a larger offset takes at least 21 clock cycles, 42 if you include backing the temporary register such an operation would take.
What may be interesting to see is how well a raycaster would work on a system that has video memory arranged into columns.
Without wishing to be typecast as that programmer who loves spinning cubes, I also wrote a cube-spinning demo to test the line drawing routines as well as some integer arithmetic routines I've added (the Z80 can't multiply or divide, so these operations need to be implemented in software).
It runs fairly smoothly (bearing in mind the 2MHz clock speed). The second half of the video has the Z80 running at 10MHz; it actually seems quite stable even though the LCD is being accessed at nearly five times its maximum speed (the system did need to be reset a few times until it worked without garbling the display).