Tali Forth for the 65c02
Re: Tali Forth for the 65c02
Thank you. I see your Github is squared away, watching and appreciate you!
Re: Tali Forth for the 65c02
noneya wrote:
Thank you. I see your Github is squared away, watching and appreciate you!
One difference between my repo and Scot's is that I have the 64tass version (master-64tass) as the main branch while Scot has the ophis version (called master-ophis in my repository) as the main branch.
I'll need to add a few words for folks who want to change their upstream repository to mine because of that difference, as there are several people I know who are using the Ophis assembler.
I plan to port any bugfixes to both versions for the time being, but any new work will be for the 64tass version which allows users to conditionally assemble only the portions of Tali they want (eg. they can get it down under 16K if that helps it fit on their system).
-
adrianhudson
- Posts: 169
- Joined: 30 Apr 2022
- Location: Devon. UK
- Contact:
Re: Tali Forth for the 65c02
I don't want to be seen as lazy but does anyone have any links to either documentation on, or sample code for BLOCK-WRITE and BLOCK-READ?
I'm a Forth newb (using Tali Forth2) and getting very tired rapidly of retyping stuff - I NEED to be able to save!
Whilst I can write the code for accessing the storage itself (I will use assembler to start with and convert to forth later - when I have learnt it!) but I just can't get started because of what appears to be a paucity of info on these functions.
I'm a Forth newb (using Tali Forth2) and getting very tired rapidly of retyping stuff - I NEED to be able to save!
Whilst I can write the code for accessing the storage itself (I will use assembler to start with and convert to forth later - when I have learnt it!) but I just can't get started because of what appears to be a paucity of info on these functions.
-
leepivonka
- Posts: 167
- Joined: 15 Apr 2016
Re: Tali Forth for the 65c02
Search the Tali Forth source for "ramdrive".
This is an example of a ramdrive based block read & write setup.
Another trick if you're using a terminal emulator on another computer:
Prepare & save some Forth text on the other computer, then use the terminal emulator to play it into the Tali console.
This is an example of a ramdrive based block read & write setup.
Another trick if you're using a terminal emulator on another computer:
Prepare & save some Forth text on the other computer, then use the terminal emulator to play it into the Tali console.
-
adrianhudson
- Posts: 169
- Joined: 30 Apr 2022
- Location: Devon. UK
- Contact:
Re: Tali Forth for the 65c02
leepivonka wrote:
Search the Tali Forth source for "ramdrive".
This is an example of a ramdrive based block read & write setup.
Another trick if you're using a terminal emulator on another computer:
Prepare & save some Forth text on the other computer, then use the terminal emulator to play it into the Tali console.
This is an example of a ramdrive based block read & write setup.
Another trick if you're using a terminal emulator on another computer:
Prepare & save some Forth text on the other computer, then use the terminal emulator to play it into the Tali console.
Re: Tali Forth for the 65c02
Hi adrianhudson,
Playing with the RAM drive will help with understanding, then you may want to do something permanent and similar to the following.
Below is an example of words I have added to my user_words.fs, which is compiled into your the Taliforth bin. You will have a copy of user_words.fs at location, or a location similar to - TaliForth2-master-64tass/forth_code
For read, basically I have a read and write assembly routine at location ED9C and EE42 respectively. In forth, assume command "2 list" is issued. The list part will fire myblockreader which I have defined by BLOCK-READ-VECTOR !
myblockreader stores the number 2 at location 15350 (I do this by choice as my assembly routine will take the number, 2 in this case and convert it from it's stored char form to decimal 2). I then clear 1024 bytes from 4FF (this is the location read bytes will load to, you can see this when you play around with the RAM drive as well) and call myblockreader1, which fires my custom assembly routine for my storage solution.
The 2 from earlier is gathered and converted from its stored character hex equivalent to a decimal number and parm'ed to my assembly routine which reads bytes from storage and writes them, starting at 4FF.
LIST then completes by reading 1024 bytes starting from 4FF to my screen.
Write follows a similar pattern, for example when I issue a "flush" to save a forth screen myblockwriter begins execution.
Above is just an example, you do not have to do it exactly this way nor do you need all the words above. I am sharing in hopes it gives some insight, and leaving in the parts I also use to call some disk command routines hoping this may also help. With those for an example:
At the Forth prompt I may issue command
rm /some_directory/somefile
As you will see above, I capture any parm after the command rm to location list_path, then kick off an assembly routine at location 60742 (hex ED46) that gathers the parm and passes command rm /some_directory/somefile to my storage solution.
Regards,
Playing with the RAM drive will help with understanding, then you may want to do something permanent and similar to the following.
Below is an example of words I have added to my user_words.fs, which is compiled into your the Taliforth bin. You will have a copy of user_words.fs at location, or a location similar to - TaliForth2-master-64tass/forth_code
For read, basically I have a read and write assembly routine at location ED9C and EE42 respectively. In forth, assume command "2 list" is issued. The list part will fire myblockreader which I have defined by BLOCK-READ-VECTOR !
myblockreader stores the number 2 at location 15350 (I do this by choice as my assembly routine will take the number, 2 in this case and convert it from it's stored char form to decimal 2). I then clear 1024 bytes from 4FF (this is the location read bytes will load to, you can see this when you play around with the RAM drive as well) and call myblockreader1, which fires my custom assembly routine for my storage solution.
The 2 from earlier is gathered and converted from its stored character hex equivalent to a decimal number and parm'ed to my assembly routine which reads bytes from storage and writes them, starting at 4FF.
LIST then completes by reading 1024 bytes starting from 4FF to my screen.
Write follows a similar pattern, for example when I issue a "flush" to save a forth screen myblockwriter begins execution.
Code: Select all
hex
: myblockreader1 ED9C execute ;
: myblockwriter1 EE42 execute ;
decimal
: store_block_num 15350 ! ;
: clear_loading 1279 1024 blank ;
: myblockreader store_block_num . clear_loading myblockreader1 ;
: myblockwriter store_block_num . myblockwriter1 ;
' myblockreader BLOCK-READ-VECTOR !
' myblockwriter BLOCK-WRITE-VECTOR !
editor-wordlist >order
hex
: TEXT PAD 258 BL FILL WORD COUNT PAD SWAP MOVE ;
decimal
CREATE list_path 64 allot
: ls 13 TEXT PAD list_path 64 move 60592 execute ;
: pwd 13 TEXT PAD list_path 64 move 60624 execute ;
: cd 13 TEXT PAD list_path 64 move 60656 execute ;
: mkdir 13 TEXT PAD list_path 64 move 60699 execute ;
: rm 13 TEXT PAD list_path 64 move 60742 execute ;
: mv 13 TEXT PAD list_path 64 move 60785 execute ;
: bload 13 TEXT PAD list_path 64 move 61232 execute ;
hex
: A1 DC12 execute ;
: A1W DC15 execute ;
: MON FC00 execute ;
: MONW FC29 execute ;
: WOZMON FC00 execute ;
decimal
: cls 12 emit ;At the Forth prompt I may issue command
rm /some_directory/somefile
As you will see above, I capture any parm after the command rm to location list_path, then kick off an assembly routine at location 60742 (hex ED46) that gathers the parm and passes command rm /some_directory/somefile to my storage solution.
Regards,
-
adrianhudson
- Posts: 169
- Joined: 30 Apr 2022
- Location: Devon. UK
- Contact:
Re: Tali Forth for the 65c02
Hi noneya,
That is EXACTLY what I needed to get me going. Thank you very, very much indeed!
Adran
That is EXACTLY what I needed to get me going. Thank you very, very much indeed!
Adran
Re: Tali Forth for the 65c02
leepivonka wrote:
Another trick if you're using a terminal emulator on another computer:
Prepare & save some Forth text on the other computer, then use the terminal emulator to play it into the Tali console.
Prepare & save some Forth text on the other computer, then use the terminal emulator to play it into the Tali console.
If you are typing live and make a mistake, note that there is CTRL-P and CTRL-N to recall history (last 8 lines are saved). If you made a mistake on line 3 of a multi-line entry, you could type CTRL-P 3 times to get back to the first line and you can press enter. Then, CTRL-N will call up the "next" line and you can press enter if it's correct or edit it if it's wrong before pressing enter. Note that you will need to exit compiling mode (using ; ) if you were compiling and want to redefine the word correctly. This only recalls lines and doesn't undo whatever you have done so far.
Also, COLD will do a cold reset and restore the system to the boot-up state (good for when mistakes or trying things used up lots of dictionary memory or messed something up), but I have a handy reset button on my hardware and use that. Then you can paste your code.
If you want to save to hardware you need:
A mapping from blocks, which are fixed at 1K size, to locations in your storage. This treats your storage like an array of 1K blocks, but your hardware may have different ideas of addressing and you need to translate.
A forth word that accepts a buffer address and a block number on the stack and reads the block into the buffer.
A forth word that accepts a buffer address and a block number on the stack and writes the buffer into the storage.
Here is an example of my EEPROM code, which is for a 24FC1025 EEPROM (128Kx8bit so 128 "blocks"). This EEPROM has an oddity where half the memory is at one I2C address and the other half is at a different I2C address, so I wrote a special word to turn a block number into the correct I2C address and offset into that half of the EEPROM.
Also, this EEPROM has 128 byte pages, so I write them 8 at a time (eeprom-pagewrite writes a single 128 byte page, and eeprom-blockwrite does that 8 times to write an entire block).
At the very bottom, you can see where I set up the vectors using my two words that read and write blocks. The BASE is set to HEX for this code and it relies on some i2c words (definitions not shown but available on request).
Code: Select all
: block2eeprom ( u -- u u ) ( blocknum -- eeprom_address i2c_address )
( Note that the i2c_address returned has already been shifted to )
( the left by one bit. The R/*W bit in bit 0 starts as a zero. )
dup 40 < if
( Blocks 0-63[decimal] )
400 * ( multiply block number by 1024[decimal] )
A0 ( use $50 [shifted left one place] as I2C address )
else
( Blocks 64-127[decimal] - no limit check )
40 - ( subtract 64[decimal] from block number )
400 * ( multiply block number by 1024[decimal] )
A8 ( use $54 [shifted left one place] as I2C address )
then ;
: eeprom-pagewrite ( addr u u -- ) ( buffer_address eeprom_address i2c_address -- )
dup >r ( save the i2c address for later )
i2c-start i2c-tx drop ( start the i2c frame using computed i2c address )
100 /mod i2c-tx drop i2c-tx drop ( send the 16-bit address as two bytes )
80 0 do ( send the 128[decimal] bytes )
dup i + ( compute buffer address )
c@ i2c-tx drop ( send the byte )
loop drop i2c-stop ( end the frame )
r> begin ( recall the i2c address and poll until complete )
dup
i2c-start i2c-tx ( start the i2c frame using computed i2c address )
0= until drop
i2c-stop
;
: eeprom-blockwrite ( addr u -- ) ( buffer_address blocknum -- )
( Write the entire block buffer one eeprom page [128 bytes] at a time )
8 0 do
over i 80 * + ( offset by eeprom pages into block buffer )
over block2eeprom
swap i 80 * + swap ( offset by eeprom pages into eeprom )
eeprom-pagewrite
loop
2drop ;
: eeprom-blockread ( addr u -- ) ( buffer_address blocknum -- )
block2eeprom dup
i2c-start i2c-tx drop ( start the i2c frame using computed i2c address )
swap ( move the eeprom internal address to TOS )
100 /mod i2c-tx drop i2c-tx drop ( send the 16-bit address as two bytes )
i2c-start 1+ i2c-tx drop ( send I2C address again with R/W* bit set )
3FF 0 do ( loop though all but the last byte )
0 i2c-rx over i + c!
loop
( Read last byte with NAK to stop )
1 i2c-rx over 3FF + c! i2c-stop drop ;
\ Connect to Fourth BLOCK words
' eeprom-blockread BLOCK-READ-VECTOR !
' eeprom-blockwrite BLOCK-WRITE-VECTOR !Code: Select all
3 LIST \ List the code in block 3 to the screen.
3 LOAD \ Load the code in block 3.-
adrianhudson
- Posts: 169
- Joined: 30 Apr 2022
- Location: Devon. UK
- Contact:
Re: Tali Forth for the 65c02
SamCoVT, where do I start? Firstly, you are a star. Thank you!
Yes, I ran into the "Tali thinking about it" during upload. I have no handshaking yet. That is my next thing to sort out.
As far as the rest of your reply, well, I too am using a 24 series eeprom. A smaller one without the oddity. Though I will probably wish I was using the larger one and will end up swapping it with the 241025. I have I/O to it working in assembler so, what with that and your code you have shared, you have done most of the work for me! I will have to look up the datasheet of the 241025 to fully understand your code but wow.
Finally, I can't resist: reinventing the wheel is all well and good, but, and I quote "...it relies on some i2c words (definitions not shown but available on request)" may I request please? As I say, I have 24 series IO working but since I am learning FORTH it really seems stupid to sweat it!
(p.s. I have wanted to get to grips with Forth since I was in my 20s and I am mid 60s now. This is the furthest I have got. I many years read and understood all the basics - at a conceptual level - but I am a practical person and never could I find and actual nuts and bolts "here's how you interface with the hardware so I always ended up giving up)
Yes, I ran into the "Tali thinking about it" during upload. I have no handshaking yet. That is my next thing to sort out.
As far as the rest of your reply, well, I too am using a 24 series eeprom. A smaller one without the oddity. Though I will probably wish I was using the larger one and will end up swapping it with the 241025. I have I/O to it working in assembler so, what with that and your code you have shared, you have done most of the work for me! I will have to look up the datasheet of the 241025 to fully understand your code but wow.
Finally, I can't resist: reinventing the wheel is all well and good, but, and I quote "...it relies on some i2c words (definitions not shown but available on request)" may I request please? As I say, I have 24 series IO working but since I am learning FORTH it really seems stupid to sweat it!
(p.s. I have wanted to get to grips with Forth since I was in my 20s and I am mid 60s now. This is the furthest I have got. I many years read and understood all the basics - at a conceptual level - but I am a practical person and never could I find and actual nuts and bolts "here's how you interface with the hardware so I always ended up giving up)
- GARTHWILSON
- Forum Moderator
- Posts: 8773
- Joined: 30 Aug 2002
- Location: Southern California
- Contact:
Re: Tali Forth for the 65c02
adrianhudson wrote:
Finally, I can't resist: reinventing the wheel is all well and good, but, and I quote "...it relies on some i2c words (definitions not shown but available on request)" may I request please? As I say, I have 24 series IO working but since I am learning FORTH it really seems stupid to sweat it!
Quote:
(p.s. I have wanted to get to grips with Forth since I was in my 20s and I am mid 60s now. This is the furthest I have got. I many years read and understood all the basics - at a conceptual level - but I am a practical person and never could I find and actual nuts and bolts "here's how you interface with the hardware so I always ended up giving up)
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?
-
adrianhudson
- Posts: 169
- Joined: 30 Apr 2022
- Location: Devon. UK
- Contact:
Re: Tali Forth for the 65c02
Thanks Garth,
As I have mentioned before (not in so many words). That page is my bible. I used it as my reference for assembler code for i2c to my onboard clock and (24xxx) memory. The forth part is wonderful too but you omit the "nitty-gritty-actual-doing-the-business-routines".
As for the book - that was what I was eluding to all those years ago - I think I read it in 1981 - it is great for learning Forth at the conceptual level but actually poking the hardware is what I am after. It nearly gets there in the I-O chapter and you get me a lot closer in your i2c page - I just need to put it all together.
As I have mentioned before (not in so many words). That page is my bible. I used it as my reference for assembler code for i2c to my onboard clock and (24xxx) memory. The forth part is wonderful too but you omit the "nitty-gritty-actual-doing-the-business-routines".
As for the book - that was what I was eluding to all those years ago - I think I read it in 1981 - it is great for learning Forth at the conceptual level but actually poking the hardware is what I am after. It nearly gets there in the I-O chapter and you get me a lot closer in your i2c page - I just need to put it all together.
- GARTHWILSON
- Forum Moderator
- Posts: 8773
- Joined: 30 Aug 2002
- Location: Southern California
- Contact:
Re: Tali Forth for the 65c02
adrianhudson wrote:
The forth part is wonderful too but you omit the "nitty-gritty-actual-doing-the-business-routines".
The code below is from my own source code that's not on my site, as far as I remember. Hopefully I got all the special characters and tabs replaced. (I do it in DOS with the DOS/ANSI IBM437 character set.) My simple Forth assembler uses the normal assembler order of mnemonic followed by operand, and it does absolutely no parsing; so mnemonics include the addressing mode, and then you comma-in the operands. This also removes the need for an assembler vocabulary, because for example Forth's AND and assembly languages' AND do not conflict, because the latter will always be something like AND#, AND_ZP, AND_ABS, etc.. (The assembler can derive the correct branch distances though.) I probably initially did those six primitives as secondaries (ie, colon definitions), but then changed them to primitives to speed things up. I hope you have your window width wide enough that the lines don't wrap.
Code: Select all
: NOOP ;
[B]
: INIT_VIA ( -- )
00110000 VIA3DDRB C! \ Bits 5 and 4 begin as outputs.
VIA3PB C_OFF ; \ Begin with SCLK low.
CODE CLK_UP LDA# 00100000 C, TSB_ABS VIA3PB , JMP NEXT , \ Set VIA3PB5.
CODE CLK_DN LDA# 00100000 C, TRB_ABS VIA3PB , JMP NEXT , \ Clear VIA3PB5.
CODE DATA_UP LDA# 00010000 C, TSB_ABS VIA3PB , JMP NEXT , \ Set VIA3PB4.
CODE DATA_DN LDA# 00010000 C, TRB_ABS VIA3PB , JMP NEXT , \ Clear VIA3PB4.
CODE I2C_DATA_OUT LDA# 00010000 C, TSB_ABS VIA3DDRB , JMP NEXT , \ Make VIA3PB4 an output.
CODE I2C_DATA_IN LDA# 00010000 C, TRB_ABS VIA3DDRB , JMP NEXT , \ Make VIA3PB4 an input.
: INIT_VIA ( -- )
00110000 VIA3DDRB C! \ Bits 5 and 4 begin as outputs.
VIA3PB C_OFF ; \ Begin with SCLK low.
: I2C_START ( -- ) I2C_DATA_OUT DATA_UP CLK_UP DATA_DN CLK_DN ;
: I2C_STOP ( -- ) I2C_DATA_OUT DATA_DN CLK_UP DATA_UP CLK_DN ;
: I2C_ACK ( -- ) I2C_DATA_OUT DATA_DN CLK_UP CLK_DN ; \ Assumed clock is low to start.
: I2C_NAK ( -- ) I2C_DATA_OUT DATA_UP CLK_UP CLK_DN ; \ "
: I2C_ACK? ( -- f ) I2C_DATA_IN CLK_UP VIA3PB C@ 00010000 AND CLK_DN \ "
I2C_DATA_OUT 0= ; \ Check data line bit. Lo=ack=true.
[H]
: RD_I2C_BIT ( n -- 2n | 2n+1 ) \ Does not affect the clock line.
2* VIA3PB C@ -4 SHIFT 1 AND + ; \ VIA pin must already be an input.
: SEND_BIT ( n -- n ) DUP 0< \ Was MSB a 1? Remember SEND_I2C_BYT uses >< .
IF DATA_UP ELSE DATA_DN THEN \ Set data appropriately (assumes VIA data bit is an output already)
NOOP CLK_UP NOOP CLK_DN ; \ then cycle the clock up and back down.
: SEND_I2C_BYT ( b -- f ) \ Make sure you don't input anything over FF. f=0 if not acknowledged.
>< \ Swap byte order so 2* puts high bit in negative flag. I2C_DATA_OUT assumed.
8 0 \ Loop 8 times:
DO SEND_BIT 2* \ Send out MSB, then shift left so next-highest bit goes out next.
LOOP DROP \ After sending 8 bits, drop what's left of the cell.
I2C_ACK? ; \ Get the acknowledge flag.
: RCV_I2C_BYT ( -- b )
I2C_DATA_IN \ Make the bit an input so the I2C device can send us data.
DUMMYCELL \ Put the seed on the stack to build the received byte on.
8 0 \ Loop 8 times:
DO CLK_UP RD_I2C_BIT CLK_DN \ Set the clock line high before reading the data line, then put clock back down.
LOOP ; \ The acknowledge or not-acknowledge bit must be sent separately.
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?
-
adrianhudson
- Posts: 169
- Joined: 30 Apr 2022
- Location: Devon. UK
- Contact:
Re: Tali Forth for the 65c02
Well, that's given me something to get my teeth into! As always Thank you Garth!
Re: Tali Forth for the 65c02
First of all, my hardware is right out of Garth's https://wilsonminesco.com/6502primer/po ... ITBANG_I2C, where I just use pullup resistors, put a 0 in the VIA output register, and then play with the DDR (Data Direction Register) to fake an open collector. I was originally using external transistors to make actual open collector outputs, but then saw Garth's solution and it was much simpler hardware-wise. The only odd thing about it is that a "0" in the data direction register (making the pin an input) results in a "1" on the bus and vice versa.
Well, my sources have both Forth and assembly versions for some of the the same words (the Forth words for clocking were a bit slow), however I wrote them before Tali had an assembler so I was just using C, (compile a byte into the dictionary) to stuff the opcodes directly into the word. Tali is an STC (Subroutine Threaded Code) Forth so you don't need to do anything special to insert assembly and you can use Forth and assembly when defining words - other Forths have special words for getting into assembly mode, like CODE (similar to : eg. immediately followed by name of new word being defined, but then followed by assembly) in Garth's code. Also, I hadn't learned about the fancy bit manipulation instructions of the 65C02 yet, so my code does the longer "read in value, OR/AND to change the bit you want, write back to hardware" method. It runs fast enough for me that I haven't tried to make it faster. I didn't originally post it because it's "ugly" code that I played with until it worked well enough and then left it 
My routines are similar to Garth's and somewhat based on an I2C library I had already written for a different micro. In particular, we handle the NAK (often used to signal the last byte of a transfer) a bit differently, and I poll (see if EEPROM is ready to talk to) after an EEPROM page write while Garth polls before reading or writing. I think both Garth's and my code should give you an idea on how to interface with hardware. Look at the commented out words in my code for the pure-Forth versions (just above the op-code assembly versions). For 8-bit registers, you'll want to know C@ and C! which fetch and store a single byte. The allow-native after some words is Tali-specific and allows short words to be inlined (the assembly code is literally copy/pasted into the target word) instead of being compiled as a JSR (for speed at the expense of memory usage). Allow-native affects the the word most recently defined and the words can't have any loops or branches in them - see the TaliForth2 manual for more info.
This code assumes SDA on bit 7 and SCL on bit 0 of a VIA port. You can put it in forth_code/user_words.fs and rebuild Tali for your platform - it will be added to your ROM and compiled by Tali on startup (there will be a slight delay at startup). You'll need to change the address to suit your hardware and block2eeprom could be simplified to just the true section of the if (and change the EEPROM I2C address there as well to (desired_I2C_address_in_hex * 2)):If you'd like to see those opcode words in Tali assember, that would be enough for me to rewrite them and post them.
adrianhudson wrote:
As for the book - that was what I was eluding to all those years ago - I think I read it in 1981 - it is great for learning Forth at the conceptual level but actually poking the hardware is what I am after. It nearly gets there in the I-O chapter and you get me a lot closer in your i2c page - I just need to put it all together.
My routines are similar to Garth's and somewhat based on an I2C library I had already written for a different micro. In particular, we handle the NAK (often used to signal the last byte of a transfer) a bit differently, and I poll (see if EEPROM is ready to talk to) after an EEPROM page write while Garth polls before reading or writing. I think both Garth's and my code should give you an idea on how to interface with hardware. Look at the commented out words in my code for the pure-Forth versions (just above the op-code assembly versions). For 8-bit registers, you'll want to know C@ and C! which fetch and store a single byte. The allow-native after some words is Tali-specific and allows short words to be inlined (the assembly code is literally copy/pasted into the target word) instead of being compiled as a JSR (for speed at the expense of memory usage). Allow-native affects the the word most recently defined and the words can't have any loops or branches in them - see the TaliForth2 manual for more info.
This code assumes SDA on bit 7 and SCL on bit 0 of a VIA port. You can put it in forth_code/user_words.fs and rebuild Tali for your platform - it will be added to your ROM and compiled by Tali on startup (there will be a slight delay at startup). You'll need to change the address to suit your hardware and block2eeprom could be simplified to just the true section of the if (and change the EEPROM I2C address there as well to (desired_I2C_address_in_hex * 2)):
Code: Select all
\ Set up the VIA for I2C. I'm using the VIA DDR method.
\ PTA7 is data
\ PTA0 is clock
hex
7F01 constant via.porta
7F03 constant via.ddra
\ Make port A an input so the bus starts idle.
: i2c-setup 0 via.porta c! 0 via.ddra c! ;
\ Data on PORTA7 (note that 0 = 1 on the I2C bus for writing)
\ : i2c-sda0 via.ddra c@ 80 or via.ddra c! ; allow-native
\ : i2c-sda1 via.ddra c@ 7f and via.ddra c! ; allow-native
: i2c-sda0
[
AD c, 03 c, 7f c, ( lda $7f03 )
09 c, 80 c, ( ora #$80 )
8D c, 03 c, 7f c, ( sta $7f03 )
] ; allow-native
: i2c-sda1
[
AD c, 03 c, 7f c, ( lda $7f03 )
29 c, 7F c, ( and #$7F )
8D c, 03 c, 7f c, ( sta $7f03 )
] ; allow-native
\ Clock is on PORTA0 (note that 0 = 1 on I2C bus)
\ : i2c-scl0 via.ddra c@ 01 or via.ddra c! ; allow-native
\ : i2c-scl1 via.ddra c@ FE and via.ddra c! ; allow-native
: i2c-scl0
[
AD c, 03 c, 7f c, ( lda $7f03 )
09 c, 01 c, ( ora #$01 )
8D c, 03 c, 7f c, ( sta $7f03 )
] ; allow-native
: i2c-scl1
[
AD c, 03 c, 7f c, ( lda $7f03 )
29 c, FE c, ( and #$FE )
8D c, 03 c, 7f c, ( sta $7f03 )
] ; allow-native
\ Clock the bus high, then low.
: i2c-clock
i2c-scl1 i2c-scl0 ; allow-native
\ Generate a START condition on the bus.
: i2c-start
i2c-sda1 i2c-scl1 i2c-sda0 i2c-scl0 ; allow-native
\ Generate a STOP condition on the bus.
: i2c-stop
i2c-sda0 i2c-scl1 i2c-sda1 ; allow-native
\ Transmit a single bit.
: i2c-txbit ( bit -- )
if i2c-sda1 else i2c-sda0 then i2c-clock ;
\ Receive a single bit.
: i2c-rxbit ( -- bit )
i2c-sda1 i2c-scl1 via.porta c@
80 and if 1 else 0 then i2c-scl0 ;
: i2c-tx ( byte -- nak )
8 0 do dup 80 and i2c-txbit 2* loop drop ( Send the byte )
i2c-rxbit ; ( Get the NAK flag )
: i2c-rx ( nak -- byte )
0 8 0 do 2* i2c-rxbit + loop ( Receive the byte )
swap i2c-txbit ; ( Send the NAK flag )
: block2eeprom ( u -- u u ) ( blocknum -- eeprom_address i2c_address )
dup 40 < if
( Blocks 0-63[decimal] )
400 * ( multiply block number by 1024[decimal] )
A0 ( use $50 [shifted left one place] as I2C address )
else
( Blocks 64-127[decimal] - no limit check )
40 - ( subtract 64[decimal] from block number )
400 * ( multiply block number by 1024[decimal] )
A8 ( use $54 [shiften left one place] as I2C address )
then ;
: eeprom-pagewrite ( addr u u -- ) ( buffer_address eeprom_address i2c_address -- )
dup >r ( save the i2c address for later )
i2c-start i2c-tx drop ( start the i2c frame using computed i2c address )
100 /mod i2c-tx drop i2c-tx drop ( send the 16-bit address as two bytes )
80 0 do ( send the 128[decimal] bytes )
dup i + ( compute buffer address )
c@ i2c-tx drop ( send the byte )
loop drop i2c-stop ( end the frame )
r> begin ( recall the i2c address and poll until complete )
dup
i2c-start i2c-tx ( start the i2c frame using computed i2c address )
0= until drop
i2c-stop
;
: eeprom-blockwrite ( addr u -- ) ( buffer_address blocknum -- )
( Write the entire block buffer one eeprom page [128 bytes] at a time )
8 0 do
over i 80 * + ( offset by eeprom pages into block buffer )
over block2eeprom
swap i 80 * + swap ( offset by eeprom pages into eeprom )
eeprom-pagewrite
loop
2drop ;
: eeprom-blockread ( addr u -- ) ( buffer_address blocknum -- )
block2eeprom dup
i2c-start i2c-tx drop ( start the i2c frame using computed i2c address )
swap ( move the eeprom internal address to TOS )
100 /mod i2c-tx drop i2c-tx drop ( send the 16-bit address as two bytes )
i2c-start 1+ i2c-tx drop ( send I2C address again with R/W* bit set )
3FF 0 do ( loop though all but the last byte )
0 i2c-rx over i + c!
loop
( Read last byte with NAK to stop )
1 i2c-rx over 3FF + c! i2c-stop drop ;
\ Connect to Fourth BLOCK words
' eeprom-blockread BLOCK-READ-VECTOR !
' eeprom-blockwrite BLOCK-WRITE-VECTOR !
decimalRe: Tali Forth for the 65c02
adrianhudson wrote:
I have no handshaking yet. That is my next thing to sort out.
First, we need room somewhere in RAM for 256 bytes of circular buffer plus two one-byte indices used to keep track of where data last went in and where it last came out (the buffer is empty if both indices are the same). I put that stuff at the end of free RAM, just before the command history buffer:
Code: Select all
hist_buff = ram_end-$03ff ; begin of history buffers
acia_buff = hist_buff-$102 ; begin of ACIA buffer memory Then, later in the code, my initialization looks like:
Code: Select all
ACIA_DATA = $7F80
ACIA_STATUS = $7F81
ACIA_COMMAND = $7F82
ACIA_CTRL = $7F83
;; Defines for the 256 byte circular buffer with two 8-bit pointers.
ACIA_BUFFER = acia_buff+2
ACIA_RD_PTR = acia_buff+0
ACIA_WR_PTR = acia_buff+1
;; Init ACIA to 19200 8,N,1
;; Uses: A (not restored)
Init_ACIA:
lda #$1F
sta ACIA_CTRL
lda #$09 ; RX interrupt on. RTS low (asserted).
sta ACIA_COMMAND
;; Initialize the buffer
stz ACIA_RD_PTR
stz ACIA_WR_PTR
; Turn on interrupts.
cli
rtsGarth's helper functions are used directly and I add the v_irq label to Garth's SERVICE_ACIA interrupt service routine and make a few minor changes:
Code: Select all
v_irq: ; IRQ handler (only handling ACIA RX)
SERVICE_ACIA:
pha
phx
lda ACIA_STATUS
;and #$07 ; Check for errors.
;bne SERVICE_ACIA_END ; Ignore errors.
and #$08 ; Check for RX byte available
beq SERVICE_ACIA_END ; No byte available.
; There is a byte to get.
lda ACIA_DATA
jsr WR_ACIA_BUF
; Check how many bytes in the buffer are used.
jsr ACIA_BUF_DIF
cmp #$F0
bcc SERVICE_ACIA_END
; There are only 15 chars left - de-assert RTS
lda #$01
sta ACIA_COMMAND
SERVICE_ACIA_END:
plx
pla
rtiCode: Select all
;; Get_Char - get a character from the serial port into A.
;; Set the carry flag if char is valid.
;; Return immediately with carry flag clear if no char available.
;; Uses: A (return value)
Get_Char:
;; Check to see if there is a character.
jsr ACIA_BUF_DIF
beq no_char_available
char_available:
;; See if RTS should be asserted (low)
;; buffer bytes in use in A from above.
cmp #$E0
bcs buf_full
lda #$09
sta ACIA_COMMAND
buf_full:
phx ; Reading from buffer messes with X.
jsr RD_ACIA_BUF ; Get the character.
plx
;; jsr Send_Char ; Echo
sec ; Indicate it's valid.
rts
no_char_available:
clc ; Indicate no char available.
rts
kernel_getc:
; """Get a single character from the keyboard (waits for key).
; """
;; Get_Char_Wait - same as Get_Char only blocking.
;; Uses: A (return value)
Get_Char_Wait:
jsr Get_Char
bcc Get_Char_Wait
rts
kernel_putc:
; """Print a single character to the console. """
;; Send_Char - send character in A out serial port.
;; Uses: A (original value restored)
Send_Char:
sei
pha ;Save A (required for ehbasic)
wait_tx: ; Wait for the TX buffer to be free.
lda ACIA_STATUS
; A byte may come in while we are trying to transmit.
; Because we have disabled interrupts, and we've just read from
; the status register (which clears an interrupt),
; we might have to deal with it ourselves.
pha ; Save the status for checking the TRDE bit later.
and #$08 ; Check for byte recieved
beq check_tx ; No bye received, continue to check TRDE bit.
; A byte was recieved while we are trying to transmit.
; Process it and then go back to checking for TX ready.
phx
lda ACIA_DATA
jsr WR_ACIA_BUF
; Check how many bytes in the buffer are used.
jsr ACIA_BUF_DIF
cmp #$F0
bcc tx_keep_rts_active
; There are only 15 chars left - de-assert RTS
lda #$01
sta ACIA_COMMAND
tx_keep_rts_active:
plx ; Restore the ACIA_STATUS value to A.
check_tx:
; Check to see if we can transmit yet.
pla
and #$10
beq wait_tx ; TRDE is not set - byte still being sent.
; Send the byte.
pla
sta ACIA_DATA
cli
rts If you get this working, it's so much easier to edit your Forth on the PC and then just paste it over to your hardware. You also can save your code on your PC. I still type directly at my board for simple things, but for any large code I will work in an editor on the PC and paste it over. That probably explains why Tali's block editor is extremely basic with only the ability to replace lines or entire screens - I always just paste the lines/screens over to my SBC.
If you develop code you always want to run at startup, you can put it in forth_code/user_words.fs to add it to your ROM (limited by ROM space).