6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Sat May 11, 2024 10:04 am

All times are UTC




Post new topic Reply to topic  [ 264 posts ]  Go to page Previous  1 ... 11, 12, 13, 14, 15, 16, 17, 18  Next
Author Message
PostPosted: Sun Nov 20, 2022 12:23 am 
Offline

Joined: Fri Feb 12, 2021 10:17 pm
Posts: 34
Thank you. I see your Github is squared away, watching and appreciate you!

_________________
Shaking out the dirty bits!

https://github.com/DonaldMoran


Top
 Profile  
Reply with quote  
PostPosted: Wed Nov 23, 2022 2:25 pm 
Offline

Joined: Sun May 13, 2018 5:49 pm
Posts: 247
noneya wrote:
Thank you. I see your Github is squared away, watching and appreciate you!
I have a bit more to do on Github but it's all documentation fixes now. I need to update all the places it mentions Scot's repository and change it over to my repository, but all of the code has been pulled and I even learned how to pull an upstream pull request into my downstream repository so I could merge the bugfix for `WORDS` that was posted to Scot's repo. I'll still keep an eye out on Scot's repo as people are still opening issues there.

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).


Top
 Profile  
Reply with quote  
PostPosted: Sat Nov 26, 2022 4:32 pm 
Offline

Joined: Sat Apr 30, 2022 7:13 pm
Posts: 159
Location: Devon. UK
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.


Top
 Profile  
Reply with quote  
PostPosted: Sat Nov 26, 2022 6:14 pm 
Offline

Joined: Fri Apr 15, 2016 1:03 am
Posts: 136
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.


Top
 Profile  
Reply with quote  
PostPosted: Sat Nov 26, 2022 6:53 pm 
Offline

Joined: Sat Apr 30, 2022 7:13 pm
Posts: 159
Location: Devon. UK
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.

Thank you. I should have thought of that.


Top
 Profile  
Reply with quote  
PostPosted: Sun Nov 27, 2022 12:34 am 
Offline

Joined: Fri Feb 12, 2021 10:17 pm
Posts: 34
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.

Code:
        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 ;


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,

_________________
Shaking out the dirty bits!

https://github.com/DonaldMoran


Top
 Profile  
Reply with quote  
PostPosted: Sun Nov 27, 2022 10:04 am 
Offline

Joined: Sat Apr 30, 2022 7:13 pm
Posts: 159
Location: Devon. UK
Hi noneya,
That is EXACTLY what I needed to get me going. Thank you very, very much indeed!
Adran


Top
 Profile  
Reply with quote  
PostPosted: Mon Nov 28, 2022 4:32 pm 
Offline

Joined: Sun May 13, 2018 5:49 pm
Posts: 247
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.
This is the standard method I use for testing code in Tali, although I do also have block support for an I2C EEPROM connected to a couple pins on a VIA. Note that you will want to use serial port handshaking or set a line-delay in your terminal software so that no characters a lost while TaliForth2 is busy thinking about the last line it got. I use one-directional handshaking on my SBC with interrupt driven receive.

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:
: 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 !


I put this in forth_code/user_words.fs (along with the words for the I2C bus you see used in the code) so it gets compiled at startup. You could also write them in assembly and then just load their addresses into BLOCK-READ-VECTOR and BLOCK-WRITE-VECTOR similar to what noneya shows. Note that my EEPROM has no filesystem so there are no files or directories in it and there are no commands like ls or cd. Only block numbers are used like:
Code:
3 LIST \ List the code in block 3 to the screen.
3 LOAD \ Load the code in block 3.

Also, the TaliForth2 documentation has a whole tutorial section explaining how blocks work and showing the Forth words to use them. Please let me know if there is something that would be useful to add to the documentation.


Top
 Profile  
Reply with quote  
PostPosted: Mon Nov 28, 2022 8:29 pm 
Offline

Joined: Sat Apr 30, 2022 7:13 pm
Posts: 159
Location: Devon. UK
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)


Top
 Profile  
Reply with quote  
PostPosted: Mon Nov 28, 2022 9:05 pm 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8433
Location: Southern California
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!

I have working example I²C code in several forms including Forth for the 24256 at http://wilsonminesco.com/6502primer/GENRLI2C.ASM .

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)

The classic place to start is Leo Brodie's excellent book "Starting Forth." If you don't have the paper one, you can read it online, somewhat modernized, but with the original cartoons: http://www.forth.com/starting-forth/index.html

_________________
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?


Top
 Profile  
Reply with quote  
PostPosted: Mon Nov 28, 2022 9:29 pm 
Offline

Joined: Sat Apr 30, 2022 7:13 pm
Posts: 159
Location: Devon. UK
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.


Top
 Profile  
Reply with quote  
PostPosted: Mon Nov 28, 2022 10:42 pm 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8433
Location: Southern California
adrianhudson wrote:
The forth part is wonderful too but you omit the "nitty-gritty-actual-doing-the-business-routines".

The EEPROM stuff in Forth is section C.  The "nitty-gritty" is what's in sections A and B above it, but it's translated into assembly language since most readers are not familiar with Forth.  Section A has 65c02 source code for running the I2C interface as shown on the 6502 primer's "circuit potpourri" page.  Section B shows what changes you'll need to make if you use different VIA port bit numbers.

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:
: 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?


Top
 Profile  
Reply with quote  
PostPosted: Mon Nov 28, 2022 10:49 pm 
Offline

Joined: Sat Apr 30, 2022 7:13 pm
Posts: 159
Location: Devon. UK
Well, that's given me something to get my teeth into! As always Thank you Garth!


Top
 Profile  
Reply with quote  
PostPosted: Mon Nov 28, 2022 11:57 pm 
Offline

Joined: Sun May 13, 2018 5:49 pm
Posts: 247
First of all, my hardware is right out of Garth's https://wilsonminesco.com/6502primer/potpourri.html#BITBANG_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.
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.

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)):
Code:
\ 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 !

decimal
If you'd like to see those opcode words in Tali assember, that would be enough for me to rewrite them and post them.


Top
 Profile  
Reply with quote  
PostPosted: Tue Nov 29, 2022 12:53 am 
Offline

Joined: Sun May 13, 2018 5:49 pm
Posts: 247
adrianhudson wrote:
I have no handshaking yet. That is my next thing to sort out.

Let's sort that out. I'll show you my solution, which is regular transmit (because the PC can always keep up) and interrupt driven receive with 256 byte circular buffer straight from Garth's interrupt primer: http://wilsonminesco.com/6502interrupts/index.html. You likely already have a full copy of this in your Tali code at platform/platform-sbc.asm as that's the code for my single board computer, so you can look there for the full file. I'll just show you the important bits.

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:
hist_buff = ram_end-$03ff  ; begin of history buffers
acia_buff = hist_buff-$102 ; begin of ACIA buffer memory 

If you already correctly defined ram_end for your computer, then you likely can use this to put it in the same place I did. Note: if you are using the Ophis assembler, that uses .alias instead of = to assign constants.

Then, later in the code, my initialization looks like:
Code:
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
        rts
This gives names to the memory locations for the buffer and the two indices (called ACIA_RD_PTR and ACIA_WR_PTR in Garth's code, but they are only 8-bits and not full pointers).

Garth'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:
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
        rti

Then my kernel_getc uses the circular buffer and kernel_putc writes bytes like normal, however kernel_putc does have to watch for incoming bytes because it reads the status register (and doing so might clear an interrupt that wasn't handled yet). That was the only wrinkle I ran into, and of course Garth's page clearly describes this scenario.
Code:
        ;; 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                       

It's worth noting that I'm using a Rockwell ACIA - if you have a WDC one, it has a hardware bug and you can't use the status register to see if the previous byte has finished transmitting. There are workarounds for this on the forum: http://forum.6502.org/viewtopic.php?f=4&t=2543 I'll also just mention that I've overclocked my 65C51 with a 3.6864 MHz (twice the speed) crystal so the above code says 19200 bps in the comment but my actual hardware runs at 38400 bps.

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).


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 264 posts ]  Go to page Previous  1 ... 11, 12, 13, 14, 15, 16, 17, 18  Next

All times are UTC


Who is online

Users browsing this forum: No registered users and 4 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to: