Jump to content
IGNORED

Pascal on the 99/4A


apersson850

Recommended Posts

I made Pascal/assembly programs once to control a lift (or elevator if you prefer). Three floors, buttons up/down, LED indicators for next floor and floor after that destinations.

Control via the PIO port. There's a picture of it in this Programbiten newsletter, page 4.

Edited by apersson850
  • Like 3
Link to comment
Share on other sites

4 hours ago, apersson850 said:

I made Pascal/assembly programs once to control a lift (or elevator if you prefer). Three floors, buttons up/down, LED indicators for next floor and floor after that destinations.

Control via the PIO port. There's a picture of it in this Programbiten newsletter, page 4.

Now that is super nifty. At first I thought you were controlling a real elevator with the TI! I wish I could read that article. Looks like a much younger version of yourself :lol:

I love the PIO port's simplicity for hardware interfacing, even for Sunday hobbyists like myself. My favorite remains the robotic arm control program I made over a decade ago, although this one was programmed in straight assembly.

However I've also done projects using XB with assembly routines support for PIO port control but it's relatively slow. Here's an example with oscilloscope graphics:

Using the pcode system is much faster and far more suited for interfacing, and I'm looking forward to see what I can come up with now that I have the RS232 library done.

 

  • Like 4
Link to comment
Share on other sites

Perhaps it's interesting to see how I used the PIO port when controlling the elevator.

In my case the port was expanded to handle 8 bits out and 8 bits in at the same time. External latches were controlled by the EXTDIR output.

 

Spoiler

;--------------------
;
; Support for PIO in- and output for lift control.
;
; Requires

; var
;   outdata,
;   indata :integer;
;   
; procedure pioreset;  external;
; procedure piostore(bit:boolean; number:integer);  external;
; function  pioload(number:integer): boolean;  external;
;
; A-DATA 860224
;
;--------------------

PIOCRU   .EQU 1300H
PCODECRU .EQU 1F00H

CARD     .EQU 0
INTDIR   .EQU 1
LATCH    .EQU 2
EXTDIR   .EQU 3
LED      .EQU 7
PIOPORT  .EQU 5000H

SP       .EQU 10

MINIMEM  .EQU 7000H


         .PROC PIORESET
         .PUBLIC OUTDATA
         .DEF PIOON,PIOOFF,SETOUT,SETIN,STOREPIO,LOADPIO
         
         MOV  R11,R7
         BL   @PIOON
         BL   @SETOUT
         CLR  @OUTDATA
         CLR  R1
         BL   @STOREPIO
         BL   @PIOOFF
         B    *R7
         
SAVCRU   .WORD 0

PIOON
         MOV  R12,@SAVCRU
         LI   R12,PCODECRU
         SBZ  CARD
         LI   R12,PIOCRU
         SBO  LED
         SBO  CARD
         B    *R11
         
PIOOFF
         SBZ  CARD
         SBZ  LED
         LI   R12,PCODECRU
         SBO  CARD
         MOV  @SAVCRU,R12
         B    *R11
         
       SETOUT
         SBZ  LATCH
         SBZ  EXTDIR
         SBZ  INTDIR
         B    *R11
         
SETIN
         SBZ  LATCH
         SBO  INTDIR
         SBO  EXTDIR
         B    *R11
         
STOREPIO
         MOVB R1,@PIOPORT
         SBO  LATCH
         SBZ  LATCH
         B    *R11
         
LOADPIO
         SBO  LATCH
         SBZ  LATCH
         MOVB @PIOPORT,R1
         B    *R11
         
         
         .PROC PIOSTORE,2
         .REF  PIOON,SETOUT,STOREPIO,PIOOFF
         .PUBLIC OUTDATA
         
         MOV  R11,R7
         BL   @PIOON
         BL   @SETOUT
         
         MOVB @OUTDATA,R1
         LI   R0,7      ;Calculate bit count to shift
         S    *SP+,R0
         LI   R3,8000H
         SRC  R3,0
         
         CLR  R4
         MOV  *SP+,R2
         JEQ  ISZERO
         INCT R4
ISZERO   X    @SETTABLE(R4)
         MOVB R1,@OUTDATA
         BL   @STOREPIO
         BL   @PIOOFF
         B    *R7
         
SETTABLE 
         SZC  R3,R1
         SOC  R3,R1
         
         
         .FUNC PIOLOAD,1
         .REF  PIOON,PIOOFF,SETIN,LOADPIO
         .PUBLIC INDATA
         
         MOV  R11,R7
         BL   @PIOON
         BL   @SETIN
         
         LI   R0,7
         S    *SP+,R0
         LI   R3,8000H
         SRC  R3,0
         
         BL   @LOADPIO
         MOVB R1,@INDATA
         CLR  *SP       ;Set result false
         CZC  R3,R1
         JEQ  ISZERO
         INC  *SP       ;Set result true
         ISZERO   BL   @PIOOFF
         B    *R7
         
         
         .FUNC FETCH,1
         MOV  @MINIMEM+30,R0
         A    *SP+,R0
         MOV  *R0,*SP
         CLR  *R0
         B    *R11
         
         .END

Here's then the unit that used the assembly routines above.

Spoiler

unit liftcontrol;
  (* Allows control of the lift simulation system *)

interface

const
  (* Out bits *)
  motor     = 0;
  direction = 1;
  yellow1   = 2;
  yellow2   = 4;
  yellow3   = 6;
  red1      = 3;
  red2      = 5;
  red3      = 7;
  
  (* In bits *)
  level1    = 2;
  level2    = 1;
  level3    = 0;
  request1  = 5;
  request2  = 4;
  request3  = 3;
  destA     = 6;
  destB     = 7;
  
procedure setbit(value:boolean; number:integer);
function readbit(number:integer):boolean;

procedure levelwait(var level :integer);
procedure liftkey(var keynum :integer);


implementation

const
  leveladdr = 28704;
  liftaddr  = 28708;

var
  outdata,
  indata :integer;
  levelsem,
  liftsem :^semaphore;

procedure pioreset;     external;
procedure piostore(bit,number:integer); external;
function pioload(number:integer):boolean;       external;

procedure loadpointer(var pointer; value :integer);     external;
function fetch(offset :integer) :integer;        external;


procedure setbit;
  (* Gives a bit a certain value *)

begin
  if value then
    piostore(1,number)
  else
    piostore(0,number);
end; (* setbit *)


function readbit;
  (* Reads any bit *)

begin
  readbit := pioload(number);
end; (* readbit *)


procedure levelwait;
  (* Waits for passed level indicator *)

begin
  wait(levelsem^);
  level := fetch(0);
end; (* levelwait *)


procedure liftkey;
  (* Waits for pressed lift key *)
  
begin
  wait(liftsem^);
  keynum := fetch(2);
end; (* liftkey *)


begin (* liftcontrol *)
  indata := 0;
  pioreset;
  loadpointer(levelsem,leveladdr);
  loadpointer(liftsem,liftaddr);
  seminit(levelsem^,0);
  seminit(liftsem^,0);
end. (* liftcontrol *)

 

Edited by apersson850
  • Like 3
Link to comment
Share on other sites

@TheBF

In any 9902 code, it helps me to have the symbol names for CRU bits like XBRE, RTSON etc. 

 

Ive been very confused about RTS/CTS.  A lot of 4A software leaves RTS asserted during a session. But I saw that    T I source code only asserts RTSON before sending a character, then turns it back off. (The 9902 waits for CTS before actually transmitting, so I guess you don't have to.) 

 

Another puzzle is: why does the 4A's RS232 card implement a CTS output?

 

I suspect the answer has to do with the 4A being wired like DCE instead of DTE. (Which is weird.) If it were acting as a DCE to another DTE, then it needs to acknowledge a RTS.  That's not symmetric. 
 

The document that clarified what I should do was on T I's guide to interfacing UART chips and the "modern" PC DB9S connector. 

  • Like 2
Link to comment
Share on other sites

1 hour ago, FarmerPotato said:

@TheBF

In any 9902 code, it helps me to have the symbol names for CRU bits like XBRE, RTSON etc. 

Me too but in Forth they take up space so I used a couple of the important ones and put a list of them in comments in my file. 

1 hour ago, FarmerPotato said:

 

Ive been very confused about RTS/CTS.  A lot of 4A software leaves RTS asserted during a session. But I saw that    T I source code only asserts RTSON before sending a character, then turns it back off. (The 9902 waits for CTS before actually transmitting, so I guess you don't have to.) 

I have seen a lot of variations in interpretation of how all those signals are used in real world equipment. So you confusion is well founded.  It's an ancient spec from a time when data transmission was having to deal with lots of new kinds of gear. With the carrier detect signal it appears that RS232 was designed for modems. ?  

Some equipment I have worked with ignores all of the handshakes and relies on really fast ISRs to catch every character in a healthy buffer and process the data faster than it can be delivered. Others handshake every character.

 

1 hour ago, FarmerPotato said:

 

Another puzzle is: why does the 4A's RS232 card implement a CTS output?

I suspect the answer has to do with the 4A being wired like DCE instead of DTE. (Which is weird.) If it were acting as a DCE to another DTE, then it needs to acknowledge a RTS.  That's not symmetric. 

The document that clarified what I should do was on T I's guide to interfacing UART chips and the "modern" PC DB9S connector. 

Hit send too soon. 

From what I see the 9902 doesn't have a CTS line, so it looks like TI built one on the card. ??

Edit: Nope. That's not true. I just looked at the chip pinout. 

So I have no idea why they did that.

 

That is a very nice document. Thanks for sharing. 

Edited by TheBF
fixed comment
  • Like 3
Link to comment
Share on other sites

10 hours ago, TheBF said:

In case someone else finds this useful, here is an MS Word doc that I found somewhere with more info on TI-99 and RS232.

 

9902.doc 93 kB · 0 downloads

Back in the late 90's, I had a nice book about rS232 and all the ins and outs. Can't think of the name though. I may still have it, but it'd be in my semi-trailer if I do, and I haven't been in that for a couple of years, one of the back doors is rusted at the hinges, so when I do open it I have to be ready to make a new door with plywood.

Edited by RickyDean
spelling
  • Like 2
Link to comment
Share on other sites

10 hours ago, TheBF said:

With the carrier detect signal it appears that RS232 was designed for modems. ?  

Yeah. DTE to DCE should be a straight-through cable. 

 

Going back to the 80s, why our RS232 card didn't have a CD input.  The workaround was to wire modem CD pin 8 to RS232/2 input DTR.  (I remember a classified ad in Micropendium, offering this open secret for $20.)

 

Going back to the era, computer manuals show what was in demand:

Terminals (DTE) with DB25 connected to a minicomputer port by about 50 feet of thick cable

Terminals (DTE) connected to modem (DCE) transmitting to modem (DCE) connected to minicomputer

Terminals (DTE) connected by current loop to minicomputer--1000 feet. 

Terminals (DECNet) - RS-423 differential, on 6P6S phone modular jack, straight through 6-conductor to minicomputer (it's beautiful)

 

The minicomputer end - Texas Instruments had a 2x9-pin male connector.  At VCFSW I was given a CI403, TI's 4-channel serial card for the 990.  There are four, 2x9 pin, male connectors, with pin 1 used a key.  It allowed a LOT of terminal ports to be crammed in.  Interesting that TI did imagine 4 ports to be a normal amount for the Home Computer!

 

The CPU card of the 990/10A has one regular DB25 for the console terminal.  I have seen TI products with one or two TMS9902 to DB25 ports, while the CI403 has four 8250s.  One TMS7000 board has a 6850 UART.

 

 

 

  • Like 2
Link to comment
Share on other sites

On 2/29/2024 at 8:38 PM, FarmerPotato said:

@TheBF

In any 9902 code, it helps me to have the symbol names for CRU bits like XBRE, RTSON etc. 

 

Ive been very confused about RTS/CTS.  A lot of 4A software leaves RTS asserted during a session. But I saw that    T I source code only asserts RTSON before sending a character, then turns it back off. (The 9902 waits for CTS before actually transmitting, so I guess you don't have to.) 

I noticed that RTS is always ON on the remote PC (Teraterm) with a null modem connection from the TI, so checking for RTS prior to receiving on the TI side is unnecessary. Is this behavior standard with null modem connections? If it is then I'll get rid of the extra code since I don't plan on using a modem for connections with the pcode system.

  • Like 1
Link to comment
Share on other sites

You are safe with that decision. I don't test RTS on receive side, I simply pull on the CTS line to keep Teraterm from overrunning TI-99. 

 

It's RPN but you can see what I use that works in non-interrupt driven receive.

Not sure how much difference it makes but I also disabled interrupts on the chance that a character would be missed because an interrupt stole the CPU's attention away from my more important stuff. 

[CC] DECIMAL [TC]
CODE KEY? ( -- n )          \  "com-key"
        0 LIMI,
        R12 RPUSH,           \ save R12 on return stack  *Needed?*
        CARD @@ R12 MOV,     \ set base address of CARD
        UART @@ R12 ADD,     \ add UART, >1300+40 = CRU address
        TOS PUSH,            \ give us a new TOS register (R4)
        TOS CLR,             \ erase it

\ use negative CRU address to reach back to CARD base address >1300
        -27 SBZ,             \ CARD CTS line LOW. You are clear to send
        21 TB,               \ test if char ready
        EQ IF,
            TOS 8 STCR,      \ read the char
            18 SBZ,          \ reset 9902 rcv buffer
            TOS SWPB,        \ shift char to other byte
        ENDIF,
        -27 SBO,             \ CTS line HIGH. I am busy!
        R12 RPOP,            \ restore old R12  *Needed?*
        2 LIMI,
        NEXT,
        ENDCODE

 

 

 

  • Like 2
Link to comment
Share on other sites

I have on occasions mentioned that I did an attempt to modify the p-system to handle pre-emptive multitasking, as a complement to the already existing volountary task switching mechanism.

I was semi-successful. The principle worked, but the system couldn't handle certain heap operations when multitasking was active. Dynamic variables are valuable in all cases and especially in such contexts. Fixing the heapops unit without the source wouldn't be feasible, so I stopped there.

I have later realized that a different approach would have been to actually implement the attach intrinsic. As it is, it's implemented in the system but it doesn't do anything.

With volountary task switching, tasks of equal priority will not hand over the CPU to another such task unless the running one is stopped in some way. If it doesn't wait for some condition, it will run forever. No other task with the same priority will ever be let in. You can accomplish pre-emptive multitasking when a semaphore can be attached to a hardware timer, by attaching a process with high priority to a semaphore that's signalled every so often. That will allow that process to execute each time the interrupt shows up, since the process has a high priority. The only thing it does is to wait for the same semaphore again, so it immediately returns to the first task in the ready queue. But if the other tasks running have the same priority, the one that's interrupted will always be put at the last position in the ready queue, so the one that has waited the longest time will run next.

 

In systems where it works, attach is used to connect a semaphore (they are already implemented) to some kind of (hardware) event. Thus the equivalent of signal could be applied to a semaphore with a timer expires or a key is pressed.

The compiler can already compile attach to the correct p-code. Since the p-code interpretation table is in RAM, it's easy to implement a new interpretation of attach. You just have to store it somewhere. Back in the 1980's I used Minimemory RAM to do that, since it's a resource the p-system doesn't normally use.

Since the interrupt execution is also in RAM when the p-system runs, it's easy enough to install a wedge there too, to allow branching to your own interrupt service. That's how I did the pre-emptive multitasking, by letting the VDP interrupt run an extra sequence to check if there were processes waiting in the ready queue and then let them start.

Now with a working attach, it would be possible to create system events like a key is available in the keyboard buffer, a timer has expired or a character has been received by the RS232 card.

There is even a solution to the problem that the RS232 card's interrupt service assumes a pointer to the reception buffer at the start of scratch pad RAM, where the p-system has the inner interpreter. At boot time, the p-system builds a table of CRU branch addresses for all interrupt service routines found. Hence it's possible to patch this branch table to our own service routine, which would run instead of the one on the card and store the received character in a queue we define. Perhaps in Mini Memory RAM, to avoid all interference with other things in the system. This means that interrupt handling could be pretty fast (the p-system only allows interrupts between p-codes) and the result (characters in the queue) could be published to the Pascal level by a signal to a semaphore.

 

I have made no math to compute a realistic reception rate yet.

  • Like 4
Link to comment
Share on other sites

Could this work with fast serial communication? At 9600 bps you have a byte coming in every millisecond or so.

16mS interrupt time would not allow very fast communication or am I missing the important part somewhere?

Link to comment
Share on other sites

Posted (edited)

The interrupt signal has to come from the card, of course. Not the VDP.

But I've not done any math about how frequently that system can respond to interrupts. The TMS 9900 doesn't execute too many instructions in a millisecond.

A rough estimate is 150, if there aren't wait states.

Edited by apersson850
  • Like 3
Link to comment
Share on other sites

7 minutes ago, apersson850 said:

The interrupt signal has to come from the card, of course. Not the VDP.

But I've not done any math about how frequently that system can respond to interrupts. The TMS 9900 doesn't execute too many instructions in a millisecond...

Ok. That make sense. 

My RS232 ISR executes in 314 cycles on a zero wait-state machine (105uS) from my counts from the 9900 manual.

Looking at Classic99 I see about 50% overhead compared to the textbook cycles. ??

But let's say it's 2X slower. 

If so that would ~210uS.  If we are sending at 19,200bps full speed we have characters hitting about every 500uS, so that could consume over 40% of the CPU. 

  • Like 2
Link to comment
Share on other sites

Did you do account for the wait-state generation in the TI 99/4A when you did your cycle math?

 

Anyway, for top speed nothing but assembly will work. Even that's tight, as your estimates show.

I was more thinking along the line of implementing an ISR that would read incoming characters and stuff them in the reception buffer. Do that, increment the buffer pointer and control the CTS pin accordingly and that's about it. Checking if there are characters available and then signal the semaphore could be done on the VDP interrupt, if one would prefer. Then I'm not sure at what rate characters could be read out from the buffer. Some experimentation would probably be required.

  • Like 1
Link to comment
Share on other sites

2 hours ago, apersson850 said:

Did you do account for the wait-state generation in the TI 99/4A when you did your cycle math?

 

Anyway, for top speed nothing but assembly will work. Even that's tight, as your estimates show.

I was more thinking along the line of implementing an ISR that would read incoming characters and stuff them in the reception buffer. Do that, increment the buffer pointer and control the CTS pin accordingly and that's about it. Checking if there are characters available and then signal the semaphore could be done on the VDP interrupt, if one would prefer. Then I'm not sure at what rate characters could be read out from the buffer. Some experimentation would probably be required.

No I counted from the book so it's the very best case which is why I doubled the number.  I don't actually know how to calculate the slow down from the the TI-99 wait state generator.

I took a look at the Classic99 dis-assembler and see between 0 difference from the text book for a register in the scratch-pad to what seems like double the cycles in some cases.

 

Yes indeed my ISR does those things and hits the CTS pin when the buffer is 1/2 full. 

Again the code below  is in RPN assembler and Forth but I think it could be worked into the Pascal environment from what you have said about working with interrupts. 

There are two big pieces.  The "installer" that sets up the magic to trick the console ISR and the actual ISR to collect data into the queue. 

 

It's all derived work from other people but it's working well now so when the time comes it might be useful to @Vorticon to translate it. 

 

Spoiler
\ RS232/1 Interrupt Handler for CAMEL99 Forth   B Fox Feb 14 2019

\ Feb 2024- make this run on CAMELTTY Forth 

NEEDS DUMP FROM DSK1.TOOLS    \ DEBUG ONLY
NEEDS MOV, FROM DSK1.ASM9900

\ *************************************************************************
\ *   Adaptation of Jeff Brown / Thierry Nouspikel (sp) idea to leverage
\ *   the ROM-based ISR to service external interrupts (RS232 in our case)
\ *   within the VDP interrupt framework.
\ *   Based on code by Insanemultitasker ATARIAGE

\ Changes:
HEX
             83C0 CONSTANT ISRWKSP
 CARD @ UART @ +  CONSTANT COM1

: (R4)    R4 () ;  \ syntax sugar for Forth Assembler 

\ ************************************************************
\ simple circular Q management
  40       CONSTANT QSIZE
  QSIZE 1- CONSTANT QMASK    \ circular mask value
 
 CREATE Q  ( -- addr)   QSIZE CELL+ ALLOT 

\ Queue pointers
  VARIABLE QHEAD
  VARIABLE QTAIL

: QCLEAR     Q QSIZE 0 FILL   QHEAD OFF   QTAIL OFF ;

\ ************************************************************
\ * QKEY? - Read character from 'Q' at index 'QHEAD'
 HEX
 CODE QKEY? ( -- c | 0 )         \ 0 means queue empty
       TOS PUSH,                 \ make space in the TOS cache (R4)
       TOS CLR,                  \ FLAG to say no char ready
       QHEAD @@ QTAIL @@ CMP,
       NE IF,                    \ head<>tail means char waiting
           QHEAD @@ W MOV,       \ get queue head index to W
           Q (W) TOS MOVB,       \ get char from Q -> TOS
           TOS SWPB,             \ move to other side of register
           W INC,                \ inc the index
           W QMASK ANDI,         \ wrap the index
           W QHEAD @@ MOV,       \ save the new index
       ELSE,
       \ queue is empty...
           CARD @@ R12 MOV,      \ can't assume R12 is correct
           5 SBZ,                \ set -CTS line LOW to get more data
       ENDIF, 
       NEXT,
ENDCODE

\ **************************************************************
\ * ISR is in workspace 83C0. ONLY R3 & R4 are free to use!!!
DECIMAL
CREATE TTY1-ISR ( *isr with hardware handshake * )
       ISRWKSP LWPI,                                           \   10
       R12 CLR,          \ select 9901 chip CRU address        \   10
       2 SBZ,            \ Disable VDP int prioritization      \   12
       R11 SETO,         \ 3.5.16 hinder screen timeout        \   10 
       R12 COM1 LI,      \ select card1+uart1                  \   12 
       QTAIL @@ R4 MOV,  \ Queue tail pointer ->R4             \   22
       16 TB,            \ interrupt received?                 \   12
       EQ IF,            \ Yes; enqueue char                   \   10
            Q (R4) 8 STCR,  \ read byte into Q                 \   52
        \  *** manage Queue pointer ***
            R4 INC,            \ bump the index                    10
            R4 QMASK ANDI,     \ wrap the index                    14
            R4 QTAIL @@ MOV,   \ save index in QTAIL               22

        \  *** test buffer status ***
            QHEAD @@ R4 SUB,   \ R4 has Qtail                      22
            R4 ABS,            \ R4 has byte count in Q            12
            R4 QSIZE 2/ CI,    \ 1/2 full?                         14 
            GTE IF,                                             \  10
            \ we can change CTS line by using a negative bit value
               -27 SBO,          \ CTS line HIGH. I am busy!       12
            ENDIF,
       ENDIF,
       18 SBO,         \ clr rcv buffer, enable interrupts         12
       R12 CLR,        \ select 9901 chip CRU address              10
       3 SBO,          \ reset timer int                           12
       RTWP,           \ Return                                    14
                       \          104.6 uS                        314 

\ *******************************************************************
\ * Configure ROM ISR to pass through external interrupts as VDP interrupts
\ *   (Jeff Brown/Thierry)

HEX
\ get address Forth's tos register (R4) so we can transfer ISR handler 
\ to the ISR workspace 
8300 4 CELLS + CONSTANT 'TOS

CODE INSTALL ( ISR_address -- )
       0 LIMI,
       83E0 LWPI,       \ select GPL workspace 
       R14 CLR,         \ Disable cassette interrupt; protect 8379
       R15 877B LI,     \ disable VDPST reading; protect 837B

       ISRWKSP LWPI,    \ switch to ISR workspace
       R1 SETO,         \ [83C2] Disable all VDP interrupt processing
       'TOS @@ R2 MOV,   \ [83C4] set our interrupt vector from Forth R4
       R11 SETO,        \ Disable screen timeouts

       R12 CLR,         \ Set to 9901 CRU base
       BEGIN,
          2 TB,         \ check for VDP interrupt
       NE UNTIL,       

       1  SBO,          \ Enable external interrupt prioritization
       2  SBZ,          \ Disable VDP interrupt prioritization
       3  SBZ,          \ Disable Timer interrupt prioritization

       8300 LWPI,       \ return to the FORTH WS
       TOS POP,         \ refill stack cache register
       2 LIMI,          \ 3.2  [rs232 ints now serviced!]
       NEXT,            \ and return to Forth
ENDCODE

DECIMAL
CODE ISRON ( uart -- )  \ * Turn on the 9902 interrupts
       0 LIMI,
       TOS R12 MOV,
       18 SBO,          \  Enable rs232 RCV int
       TOS POP,
       2 LIMI,
       NEXT,
ENDCODE

CODE ISROFF ( uart -- )  \ * Turn off the 9902 interrupts
       0 LIMI,
       TOS R12 MOV,      \ i.e., >1340
       18 SBZ,           \ Disable rs232 rcv int
       TOS POP,
       2 LIMI,
       NEXT,
ENDCODE

: ISR-I/O
      QCLEAR            \ reset Queue pointers, erase data
      KEY? DROP         \ clear any char from 9902
      COM1 ISROFF       \ just to be safe
      TTY1-ISR INSTALL
      ['] QKEY? >BODY  ['] KEY? !  \ patch KEY?' to read the queue
      COM1 ISRON ;

CR .( Intalling ISR on port TTY1 ...)
ISR-I/O 
CR .( ISR recieve enabled)
CR 

 

 

 

  • Like 2
Link to comment
Share on other sites

On 3/1/2024 at 3:38 AM, FarmerPotato said:

Ive been very confused about RTS/CTS.

There is a reason that getting serial communication to work was considered among the most difficult things you could take on when I started to learn about computers in the late 1970's. I have a full book that's about nothing else but connecting V.24/RS 232C.

  • Sad 1
Link to comment
Share on other sites

45 minutes ago, TheBF said:

No I counted from the book so it's the very best case which is why I doubled the number.  I don't actually know how to calculate the slow down from the the TI-99 wait state generator.

In the TMS 9900 documentation you have the basic number of clock cycles given. Reading the instruction itself is included, but on the TI 99/4A, reading the instruction has four wait states unless the instruction is in the console's ROM or in the RAM PAD (>8300). You have to add that. Normally you have the registers in RAM PAD so they don't add anything.

In that same table, there are address modifications of type A and B. If any of those applies, then you add the clock cycles from A and/or B to the basic number of cycles.

Next you look at how many memory accesses there are associated with A and B. If these acceses go elsewhere than to ROM or RAM in the console, you have to add four wait states for each cycle there too.

 

This is why an instruction using more advanced addressing easily can grow from 14 to 30 cycles. It's also the reason for why internal memory expansion on 16-bit wide bus can increase speed with up to 110%, if you have both code and registers in expansion RAM.

  • Like 4
Link to comment
Share on other sites

2 hours ago, apersson850 said:

In the TMS 9900 documentation you have the basic number of clock cycles given. Reading the instruction itself is included, but on the TI 99/4A, reading the instruction has four wait states unless the instruction is in the console's ROM or in the RAM PAD (>8300). You have to add that. Normally you have the registers in RAM PAD so they don't add anything.

In that same table, there are address modifications of type A and B. If any of those applies, then you add the clock cycles from A and/or B to the basic number of cycles.

Next you look at how many memory accesses there are associated with A and B. If these acceses go elsewhere than to ROM or RAM in the console, you have to add four wait states for each cycle there too.

 

This is why an instruction using more advanced addressing easily can grow from 14 to 30 cycles. It's also the reason for why internal memory expansion on 16-bit wide bus can increase speed with up to 110%, if you have both code and registers in expansion RAM.

Yes, I knew about the A and B address modifications so my numbers in the code reflect that. The dirty details of how bad it gets with TI-99 expansion RAM is where I stopped. 

However if the speedup can be 110% then using a 2X more cycles as a "rule of thumb" is reasonable I guess. 

Link to comment
Share on other sites

I've changed the GETBYTE routine from a function to a procedure so it can detect when a file transmission has ended by checking the receive buffer and set a flag. That way the host program can tell when the transfer is complete. Seems to work well.

 

.proc   getbyte,2
;receive a byte from the serial port
;sets flag if sender times out

        .ref    pcodeon,pcodoff,rs232on,rs232of,uartdis,procret,cruadr
        mov     r11,@procret
        bl      @pcodoff
        bl      @rs232on
        mov     *r10+,r1        ;get flag pointer
        clr     r3              ;clear timeout flag
        mov     *r10+,r2        ;get the pointer to the byte variable
        a       @uartdis,r12    ;uart base cru address
        li      r0,30000        ;countdown timer
        sbz     -27             ;activate cts line
chkbuf  dec     r0              ;activate timeout timer
        jne     wait
        li      r3,1            ;set timeout flag if counter is 0
        jmp     exitsub
wait    tb      21              ;check if receive buffer is empty
        jne     chkbuf
        stcr    r6,8            ;get byte into r6
        sbo     -27             ;inactivate cts line
        sbz     18              ;reset buffer cru bit 21
        swpb    r6
exitsub mov     r6,*r2          ;store byte
        mov     r3,*r1          ;save timeout flag
        bl      @rs232of
        bl      @pcodeon
        mov     @procret,r11
        b       *r11

 

Here's a universal file receive program based on that. It can get either a text or binary file.

(* receives any file from a remote computer *)
(* by walid maalouli - march 4, 2024 *)

program filetran;
type
 byte = 0..255;

byteword = record
 case boolean of
  true : (value : integer);
  false : (bytes : packed array[1..2] of byte);
end; (* byteword *)

var
 rval, sval : byteword;
 flag, key, i, nbyte : integer;
 outfile : file of integer;
 fname : string;

procedure setrs232(base : integer); external;
procedure getbyte(var n, flag : integer); external;
procedure bitset(crubit, state : integer); external;
function getkey : integer; external;

begin
 page(output);
 setrs232(1);           (* select first rs232 card *)
 bitset(5, 1);          (* inactivate cts line *)
 writeln(chr(7),'destination file:');
 readln(fname);
 rewrite(outfile, fname);
 
 writeln(chr(10),chr(7),'initiate transfer and press a key');
 repeat
  key := getkey
 until key <> 255;

 getbyte(rval.value, flag);
 nbyte := 0;
 i := 0;
 gotoxy(1,14);
 writeln('bytes received:');
 
 while flag = 0 do
  begin
   (* integers always written as words, so for a file
      the incoming integers have to be converted to bytes and
      concatenated with 1 word = 2 integers *)
   
   i := succ(i);
   if i < 3 then
    begin
     nbyte := succ(nbyte);
     gotoxy(1,15);
     writeln(nbyte);
     sval.bytes[i] := rval.bytes[2]; 
    end
   else
    begin
     outfile^ := sval.value;
     put(outfile);
     i := 1;
     sval.bytes[i] := rval.bytes[2];
     nbyte := succ(nbyte);
     gotoxy(1, 15);
     writeln(nbyte);
    end;
   getbyte(rval.value, flag);
  end;
 
 (* this section handles the case of an
    odd number of bytes in a binary file *)
 if i = 1 then
   sval.bytes[2] := 0;
              
 outfile^ := sval.value;
 put(outfile);
  
 writeln(chr(10),'transfer done!');
 close(outfile, lock);
end. (* filetran *)

 

Now there is still an issue I have not been able to figure out. The second byte of any file is always skipped, so the received file is 1 byte short. There is no reason that I can see for this to happen, at least from a software standpoint. Could there be a hardware reason for this? This is no small problem, particularly for binary files...

I know I know I really should just get that XMODEM program done already which will likely solve that, but I still would like to know why this is happening...

  • Like 3
Link to comment
Share on other sites

That's a strange bug for sure. 

 

Something I notice is that you are shuffling the data across a few registers.

You could be reading more directly to the final destination.  Not sure it fix the bug but it will reduce the time spent in getbyte.

STCR *Rx,8  
INC   Rx

 

or maybe (have not tried this but I think it's valid)

STCR *R10+,8

That way the data goes into the buffer directly with auto incrementing.

 

Link to comment
Share on other sites

9 hours ago, Vorticon said:

Now there is still an issue I have not been able to figure out. The second byte of any file is always skipped, so the received file is 1 byte short. There is no reason that I can see for this to happen, at least from a software standpoint. Could there be a hardware reason for this? This is no small problem, particularly for binary files...

 

In getbyte, if the MSB of R6 is ignored, this is not a problem, but, as far as I can tell, the MSB of R6 is indeterminate unless it has been cleared before invoking the procedure.

 

...lee

  • Like 2
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...