After helping scotws get Tali Forth 2 up to version 1.0, I decided my next adventure was to start
using Forth. As it turns out, writing software in forth is quite a bit different than writing forth itself (in assembly), so it's been a good adventure so far. I know I enjoy reading some of the other folk's musings on their forth projects, even if I don't always say anything in their thread, so I figured I'd start a thread to document my progress. I'm currently working on FAT32 support for my SBC.
*caution* - I tend to be verbose. Skip to the bottom for the short version.
I added a CF card to my SBC and talk to it in 8-bit IDE mode. It connects directly to the data/address bus, and my address decoding GAL already had a spare select line as well as having separate active low *RD and *WR signals which I was planning to use with some x86 type chips (that I ended up not using). This was all that was required to get the hardware working. I used
http://www.waveguide.se/?article=8-bit- ... -interface and the pinout for an IDE cable (I'm using a compact flash to IDE adapter) to get everything hooked up.
I've been implementing words for locating and reading files from a FAT32 partition (working from information found at
https://www.pjrc.com/tech/8051/ide/fat32.html). I have it working enough that I can get a listing of the root directory (although my code should work with any directory if you know the starting cluster#) and can print a file to the screen.
I have since refactored my code twice and it's only just starting starting to look "forthy". I'm finally able to just "read" forth the same way I read C. I'm also starting to be able to see when/where words should be decomposed and I now seem to get the stack arguments in the right order for my new words (there is definitely a wrong order). When I started writing the code to locate a particular file name, I realized that it looked a lot like the directory listing I just wrote, so I was able to refactor that code to be reusable for both purposes.
My favorite thing so far is that, when having trouble with a particular word, I can just paste lines of its definition into forth and see what happens on the real hardware. .S and DUMP are very powerful to help with this. I can also try several solutions and pick the one I like best. In Brodie's "Thinking Forth", there's a quote attributed to Dr. Michael Starling that says "You don't know completely what you're doing till you've done it once. In my own experience, the best way to write an application is to write it twice. Throw away the first version and chalk it up to experience." I totally understand that now (although it takes me 3-4 tries due to lack of experience).
I have a hobby machine shop, and I find using Forth to be very similar. Sometimes I start a project not knowing what tools I will need, and when I get to where I need to use a tool I don't own, I will usually make it (which often takes longer than the original project I was working on). Sometimes I have to make the tool a couple of times (due to unforeseen (or sometimes ignored) circumstances or lack of skill), but then I have that tool for the next time I need it. Forth feels like it's the same way. I don't know what words I will be using or creating when I start a project, and it's a series of little puzzles along the way.
The short version:I'm currently in the process of rewriting my FAT32 code to support multiple files open at the same time, so things are a little unstable at the moment. Once I get things a little more stable, I will post my code for others to look at/comment on/use. To get started, here is my code for reading sectors from the compact flash. I haven't implemented writing yet. I will likely split this up into smaller words once I know what is in common between reading and writing.
Code:
\ PROGRAMMER : Sam Colwell
\ DATE : 2020-01
\ DESCRIPTION : Compact Flash card support using 8-bit interface
\ LICENSE : CC-BY 4.0
hex
7F40 constant cf.base ( Change this for your hardware )
cf.base constant cf.data
cf.base 1+ constant cf.feature ( Error Register when read )
cf.base 2 + constant cf.sectorcount ( Config for SetFeatures command )
cf.base 3 + constant cf.lba ( low byte )
\ A note about the LBA address - it's 28 bits, but we can't use a
\ double in Tali to store it because the two 16-bit halves are in the
\ wrong order in Tali. We CAN use two regular 16-bit cells because
\ they are little endian like the compact flash card expects for the
\ LBA #, but will have to transfer them separately. I will likely
\ use a double and then split it to load the LBA.
cf.base 6 + constant cf.drivehead
( bit 6 : set to 1 = LBA mode )
cf.base 7 + constant cf.status ( Command Register when written )
( bit 7 : 1 = BUSY )
( bit 6 : 1 = RDY [ready] )
( bit 3 : 1 = DRQ [data request] data needs to be transferred )
: cf.busywait begin cf.status c@ 80 and 0= until ;
: cf.init ( -- ) ( Initilize the CF card in 8-bit IDE mode )
\ Reset the CF card.
04 cf.status c!
cf.busywait
\ Set feature 0x01 (8-bit data transfers)
01 cf.feature c!
EF cf.status c!
;
: cf.read ( addr numsectors LBA.d -- )
( Read numsectors starting at LBA [double] to addr )
\ Treat LBA double as two 16-bit cells.
\ MSB is on top. We can write this as a 16-bit cell to
\ cf.lba+2, however we need to make sure we don't screw
\ up the Drive and LBA bits (in the MSB)
EFFF and ( make sure Drive is zero )
E000 or ( make sure LBA and reserved bits are 1 )
cf.lba 2 + ! ( store high word of LBA )
cf.lba ! ( store low word of LBA )
\ stack is now just ( addr numsectors )
\ Tell the CF card how many sectors we want.
dup cf.sectorcount c!
\ Send the command to read (0x20)
20 cf.status c!
cf.busywait
\ stack is still ( addr numsectors )
\ Read all of the sectors.
0 ?do
\ Read one sector - 0x200 or 512 bytes
200 0 do
\ Read one byte.
cf.data c@ over c!
\ Increment the address.
1+
loop
cf.busywait \ Wait for next sector to be available.
loop
drop ( the address )
;