Jump to content
IGNORED

Interrupt Service Routine and the RS232


Recommended Posts

For quite some time I have been mulling over ways to improve character reception in a TI terminal emulator.

Most terminal emulators combine the TI interrupt service routine and the RS232's circular interrupt buffer to receive characters.

During character reception, the TI ROM ISR first determines if an external interrupt has occurred. If so, it must scan the peripheral cards one at a time to find the interrupt. Once the proper RS232 port is located, it must execute the card's interrupt service routine. The RS232's interrupt routine populates the 254 byte circular buffer that resides in VDP memory.

Unfortunately, this whole process requires a lot of overhead and starts to fail miserably at speeds above 4800bps. Not only is the buffer size too small, all of the processing required to scan the card, handle the interrupt, and stuff it into VDP, require too much time. TIMXT could not exceed 4800bps successfully - at 9600, the TI spent 100% of its time servicing the interrupts, dropping characters along the way.

Implementing hardware flow control was an option but it meant building a special cable that many people wouldn't care to make. I went through the same scenario with my Geneve terminal emulator: PORT can sustain 38.4kKbps without a special cable; it only requires handshaking when displaying color text mode (using the V9938 graphics mode and its slow character plotting) or when transferring files with Ymodem-G. But I digress.

Months later I was looking at some information on Thierry's site when I came across an interesting article. It described an ingenious method to manage external interrupts using (abusing) the ROM interrupt service routine:

http://www.unige.ch/medecine/nouspikel/ti99/jeff.txt

This idea resonated with me. In PORT, I hijacked the system's interrupt vectors so that I could process external interrupts, video, and keyboard with no system overhead. This was "easy" to do with an OS in RAM. But the TI ROM is..well..ROM!

I was skeptical at first; will there be much of an improvement?

It took me a day or so to modify the emulator. I reserved a 4K buffer to stash incoming data. Two routines are required: an interrupt-driven RS232 capture routine and a buffer emptying routine. Think of the buffer like a bucket: one routine fills the bucket with characters; the other routine draws them out for display and other purposes. Only the active RS232 is checked and it is given immediate priority in the user ISR.

Once complete I did some base testing with my Geneve. I then asked Omega to test the program at 9600bps. He reported success. He then told me he used 19.2 with success! So, I added an option for 38.4K and surprisingly, it worked! With no hardware handshaking or cable magic!

There are a few considerations:

1. Some peripherals rely upon GPLWS R15. Jeff's method changes this value, so disk and other peripherals may fail. My solution was to modify DSRLNK to turn off RS232 interrupts and restore R15 prior to calling the ROM routine. The environment is restored afterwards.
2. All VDP-based automatic processing is inhibited. No sound, no quit key, no 50/60hz timers. You can enable the VDP interrupts and service them if you restore things to 'normal', and then re-enable the interrupt handler when complete. You cannot have both operating at the same time. I suppose a 9901-based 60hz timer may work, I'll have to try that some day.
3. Keyboard scanning takes a long time. For time-sensitive RS232 input, I use a modified direct keyboard scanning routine that can be interrupted in between steps.

My next challenges are to improve the keyscan routine and optimize the display interpreter. Later a uni-directional flow control may be needed especially when there is so much data being displayed so quickly.

  • Like 11
Link to comment
Share on other sites

The F18A GPU might be a good candidate for the display interpreter. Push the raw data to a specific VRAM buffer and trigger the GPU to process and display the data. Just a thought.

I might give that a shot down the road. First I want to optimize the interpreter and get it working more like a state machine. There is just something about pushing the limits of the raw hardware (where possible) that is a fun challenge.

 

I also want to post the basic interrupt/rs232 code here so that others can see the nuts and bolts. It's a little "harder" to do that since I moved my development to the real hardware. (i.e., using the Geneve to edit/assemble and the F18A Ti for testing)

Link to comment
Share on other sites

  • 2 weeks later...

Since my own 99/4A has RAM shadowing console ROM, I would have better abilities to do this on a real 99/4A, I presume. I can copy console ROM, change interrupt vectors and run from there, if I want to.

Yes. That would be optimal for your case where you have RAM shadowing ROM. You could change the interrupt vector and bypass the on-board ISR entirely with one of your own design.

Link to comment
Share on other sites

I made a demonstration once, at a Programbiten user's group meeting, of two assembly processes that were running concurrently. The were running under the control of a task switching program. First it modified the console ROM, after copying it to shadow RAM, to make the TMS 9901 PSI interrupt trigger the pre-emptive task switching algorithm. Then it used the timer inside the 9901 to set the run time quota, and finally launched the two processes. Both provided output to the screen, one character at a time, so the spectators could see it alternating between A and B.

 

Perhaps two or three of those attending the meeting actually understood what was going on.

  • Like 2
Link to comment
Share on other sites

  • 5 months later...

Yesterday I finally had a chance to copy my Geneve development platter (backup!) and export the RS232 routines related to this post.

 

There are some things I still plan to do to improve performance, such as code optimization and use of SAMS or 8K Superspace for incoming buffers. Since those future plans don't change the concept, I updated a few of the comments and am posting here for those interested.

 

 

*      3.3.2016  TT
*
*      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.
*
*      This approach allows the standard, unmodified TI/99-4A to capture
*      RS232 data at a maximum speed of 38.4Kbps.  Buffer over-runs and
*      data loss may occur depending on calling routine and how the data
*      is processed. 
*
*      BL   @RSINIT      Set up RS232, clear buffer pointers, configure ISR
*      BL   @RCV         Extract a character from buffer
*      BL   @RCVON       turn rcv ints on
*      BL   @RCVOFF      turn rcv ints off
*      BL   @KEYOUT      transmit a character
*
*      The DSR-based RS232 circular interrupt buffer is slow, buffers to VDP,
*      holds at most 254 characters, and relies upon the ROM ISR finding the
*      proper card to service the interrupt. Characters are dropped at
*      4800bps and above.
*
*      Using Jeff's idea, the following routines capture data to CPU RAM
*      on an interrupt basis.  The RS232 interrupt is polled directly
*      during servicing based on the current active port. Buffer capture
*      continues while the main program is processing received data.
*---------------------
* Per Jeff/Thierry:
*      Assumes that VDP reg 1 has bit >20 set so that the VDP interrupts
*      Counter-intuitive because we won't actually process these interrupts.
*      We must wait for the first one to occur, then never clear it.
*      Necessary for hack to work correctly!
*--------------------
* Implementation Notes:
*      Some peripheral cards, including TI and Myarc controllers rely
*      upon GPLWS R15 for VDP access.  The DSRLNK routine has been
*      modified to save/restore R15 and disable/enable interrupts (LIMI 0/2)
*      directly before calling the DSR ROM routine. It may be necessary
*      to turn off the RS232 interrupt (or use handshaking) if a DSR
*      tries to do a LIMI 2 while servicing.  (should not happen!)
*
*      Keyscan routine has been modified to allow RS232 servicing by
*      adding LIMI 2/0 within the routine itself.
*
*      Many TI programs call the KSCAN and other processing routines
*      within a tight loop. It is beneficial in timing-sensitive
*      programs to limit KSCAN and other similar routines, to maximize
*      the time servicing interrupts. This can be done with a state
*      machine approach, where CPU-intensive (or interrupt disabling)
*      routines are only called a fraction of the time.
* 3.3.16    config routine copied from Thierry's site.
* 3.3.16    Initial RS232 routines implemented
* 3.7.16    buffer pointers moved into scratchpad where CIB used to live
*           Cleaned up interrupt handler (removed extra CRU operations)
* 3.18.16   Consider using rcvoff/on to shut down all 4 RS232 port interrupts
*           during a port configuration.
*           Review the need to turn off interrupts on the current card when
*           changing to a new RS232 port.
*
* Future use
INTENABLE
       RT
INTDISABLE
       RT
*--------------------------------------------------------------------
* Init RS232,buffers,CIB. Destroys R5; R12
*      Only configure one RS232 as defined by BASE and PORT
*      If we switch BASE or PORT, the previous RS232 will be left with
*           interrupts enabled.  May need to track this and disable
*           the previous card.
*
RSINIT LIMI 0            inhibit ints until setup is complete
       MOV  @BASE,R12
       A    @PORT,R12
       MOV  R12,@MANIP3+2     self-modify interrupt handler R12 to save
*                             clock cycles within the int handler
       SBO  >1F          Reset 9902
       LI   R5,>0200
INIT1  DEC  R5           delay
       JNE  INIT1
       SBZ  >0D          Bit 13, disable interval register
       LDCR @CREG,8      set 8n1
       LDCR @BREG,>0C    set baud
       SBO  >12          Enable RCV Interrupt
*      LIMI 2            *may separate rsinit at later date
**     RT
* Reset circular buffer pointers
PATISR LI   R0,BSTART
       MOV  R0,@RFIRST
       MOV  R0,@RLAST
       CLR  @RBYTES
**     RT

* Configure ROM ISR to pass through external interrupts as VDP interrupts
*   (Jeff Brown/Thierry)
**CONFIG LIMI 0
       LWPI >83E0
       CLR  R14          Disable cassette interrupt; protect 8379
       LI   R15,>877B    disable VDPST reading; protect 837B
* Munge INTWS
       LWPI >83C0
       SETO R1           [83C2] Disable all VDP interrupt processing
       LI   R2,INTRPT    [83C4] set our interrupt vector
       SETO R11          Disable screen timeouts
       CLR  R12          Set to 9901 CRU base
SYNC   TB   2            check for VDP int
       JEQ  SYNC           wait until ready
       SBO  1            Enable external interrupt prioritization
       SBZ  2            Disable VDP interrupt prioritization
       SBZ  3            Disable Timer interrupt prioritization
       LWPI WS#1         switch to the main WS
       LIMI 2            3.2  [rs232 ints now serviced!]
       RT                and return

**********************************************************
* Interrupt Handler
*    Entered from the ROM ISR via the user defined interrupt
*      We immediately test the configured RS232 for a received character.
*      MANIP3 is used during setup to eliminate the need to move the base
*      and port into R12, saving some precious time.  Move to PAD for further
*      speed enhancements.
* (30 bytes)
INTRPT LWPI >83C0        R12 should still be clear upon entry
       CLR  R12          but we will clear it anyway to be safe
       SBZ  2            Disable VDP int prioritization
       SETO R11    3.5.16 hinder screen timeout (should never happen)
MANIP3 LI   R12,>1340    3.7.2016; base/port set inline for maximum speed
       TB   16           rcvint?
       JEQ  GET2         Yes; go service the rcv buffer
       CLR  R12          No; test other possible sources:
*      TB   1            external int? [assume above captures them]
*      JNE  EXTINT       **dangerous; lockup w/spurious ext. int.
*                        **be sure to turn off rs232 ints for all cards during setup
       TB   3            timer int?
       JNE  TIMINT
       RTWP              Nothing to do here. Return.
TIMINT SBO  3            reset timer latch int   (essentially ignore it)
       RTWP              return
*--------------------------------------------------------
* RS232 Circular Buffer character reception
*      Only test interrupts on active port as defined during setup
*      Spurious ints from another RS232 will result in virtual lockup
*         because they will never be serviced.
*      Future: optimize routine. Consider PAD RAM for this handler.
*  (46 bytes)
MANIP2
GET2   STCR R3,8              get character from RS232 (R12 set @manip3)
       SBO  >12
       C    @RBYTES,@BMAX     buffer at max?
       JEQ  RSINX             yes, trash the rcvd character. 
       MOV  @RLAST,R4         get current ^loc pointer
       CI   R4,BEND           at end?
       JL   ADDROK            no
       LI   R4,BSTART         yes, wrap
ADDROK MOVB R3,*R4+           stuff char into buffer
       MOV  R4,@RLAST         save next buffer loc
       INC  @RBYTES           inc total bytes in buffer
       CLR  R12
*      NOP               debug
       RTWP
RSINX  SBO  >12          Just to be safe
       CLR  R12          Reset for re-entry (probably not needed any more)
       RTWP
***************************************************
* RCV - Extract character from circular buffer. This routine
*       is called from the main program to check for and process
*       a character from the buffer.  The interrupt handler is expected
*       to add characters to this buffer even during 'extraction' so long
*       as the buffer has space.
*       Out: R7MSByte=character; 0=no char
*     
*
* 3.17 To allow NULL in R7, should test RBYTES and pass EQ bit to caller
*
* Future: Configure for (1) 8K superspace buffer (2) SAMS buffer
*         Verify null characters pass through
*
* (34 bytes)
RCV    LIMI 2            Force interrupts on (just in case they were off)
       CLR  R7           FLAG to say no char to process
       ABS  @RBYTES      3.17, can we rely upon EQ bit here?
       JEQ  EMPTY
***      LIMI 0            3.3- rcv int doesn't touch RFIRST. Allow ints.
       MOV  @RFIRST,R0   get current pointer
       MOVB *R0+,R7      get character from buffer and advance pointer
       CI   R0,BEND      now at end of buffer?
       JNE  NOWRAP       no
       LI   R0,BSTART    yes, wraparound to start
NOWRAP MOV  R0,@RFIRST   save updated buffer pointer
       DEC  @RBYTES      one fewer character in the buffer
***EMPTY  LIMI 2            enable ints (3.3-not needed now)
EMPTY  RT
BSTART EQU  >3000
BEND   EQU  >3FF0
BMAX   DATA >0FEF        one less than BEND-BSTART
* Buffer pointers in scratchpad
RFIRST EQU  >8300        Initialized during setup
RLAST  EQU  >8302        "    "
RBYTES EQU  >8304        "    "
INTFLG DATA 0            current rcv interrupt status 1=on; 0=off
* Turn on the 9902 interrupt.
RCVON  LIMI 0
       MOV  @BASE,R12
       A    @PORT,R12
       SBO  >12          Enable rs232 RCV int
       SETO @INTFLG
       LIMI 2
       RT
* Turn off the 9902 interrupt.
RCVOFF LIMI 0
       MOV  @BASE,R12
       A    @PORT,R12         i.e., >1340
       SBZ  >12          Disable rs232 rcv int
       CLR  @INTFLG
       LIMI 2
       RT
****************************************
* Xmit a character from R6-MSByte
*  Future: timeout loop and handshaking options
*
KEYOUT MOV  @BASE,R12
       SBO  7            turn on pretty light
       A    @PORT,R12
XMIT2  LIMI 0
       SBO  >10
XMIT1  TB   >16          timeout loop in future...
       JNE  XMIT1
       LDCR R6,8
       SBZ  >10
       LIMI 2
       MOV  @BASE,R12
       SBZ  7
       RT
* eof
Edited by InsaneMultitasker
  • Like 3
Link to comment
Share on other sites

Wow! Really nice! :thumbsup:

 

Here's a fast keyboard scanning routine. The interesting stuff starts at the label key_scan

 

 

;
; full, stand-alone keyboard scan - not ROM/GPL dependant
; Mark Wills January 2014
; 
 
FBFORTH EQU 0       ; set to 1 to assemble for fbForth
TURBOF  EQU 1       ; set to 1 to assemble for TurboForth
 
    IF FBFORTH
stack   equ r9      ; assembling for TI Forth
next    equ r15
    ENDIF
    
    IF TURBOF
stack   equ r4      ; assembling for TurboForth
next    equ r12
    ENDIF
  
        AORG >2000              ; note: can run from any address
 
        DEF KSCAN               ; turboforth word
        DEF VBLANK              ; set to an odd value to defeat screen blanking
        DEF KSTAT               ; returns 0=no key. 1=new key. -1=same key
        DEF KEYUP               ; set to non-zero to force key-up detection
        DEF ARDEL1              ; long delay before auto-repeat starts
        DEF ARDEL2              ; short delay between auto-repeats
 
        ; Note: To disable auto-repeat set ARDEL1 to -1
        ; Note: For auto-repeat to work, ARDEL1 should be set to a positive
        ;       value, ARDEL2 should be set to a positive value, and key-up
        ;       detection should be enabled (i.e. set to a non-zero value).
 
VBLANK      bss 2               ; flag to enable/disable blanking
KSTAT       bss 2               ; holds KSCAN status
prev_key    bss 2               ; holds previous key
keyup       bss 2               ; flag to determine if keyup events are wanted
ardel1      bss 2               ; long delay before auto-repeat starts.
ardel2      bss 2               ; short delay between auto-repeats
delay       bss 2               ; delay 1 counter (reloaded from ardelay)
 
KSCAN       data again          ; Forth CFA
again       bl @key_scan        ; do the keyboard scan
            ci r0,-1            ; no key?
            jne cont            ; if not, then continue
            clr @kstat          ; no key. zero kstat
            jmp do_kstat        ; update previous key and push to stack
            
cont        c r0,@prev_key      ; compare keypress to previous keypress
            jne not_same        ; jump if not the same
            mov @keyup,r1       ; same key. check if keyup needed?
            jeq no_keyup        ; if not then skip
            mov @delay,r1       ; check if auto-repeat enabled
            jle again           ; just scan again if not enabled
            dec @delay          ; decrement long delay counter
            jne again
            mov @ardel2,@delay
            
no_keyup    seto @kstat         ; same key. otherwise set kstat to -1
            jmp do_kstat
not_same    mov @ardel1,@delay  ; reset auto-repeat counter
            li r1,1             ; kstat (1=new key)
            mov r1,@kstat       ; new keypress
do_kstat    mov r0,@prev_key    ; update prev_key with this keypress
            dect r4             ; make space on stack
            mov r0,*stack       ; push to stack
            mov @vblank,@>83d6  ; enable/disable video blanking
    IF TURBOF
            li next,>8326       ; restore pointer to NEXT
    ENDIF
            b *next             ; exit
 
key_scan
        ; get column 0 and save for later...
            clr r0              ; column 0
            clr r6              ; byte ops
            li r12,>24          ; keyboard decoder address
            ldcr r0,3           ; select the column
            li r12,6            ; address of first row
            stcr r6,8           ; read 8 rows into r6
 
        ; check columns 1 to 5...
            clr r1              ; byte operations
            li r0,>0100         ; starting column (column 1)
            li r2,8             ; index into ascii lookup table
next_column li r12,>24          ; keyboard decoder address
            ldcr r0,3           ; select the column
            li r12,6            ; address of first row
            stcr r1,8           ; read 8 rows into r1
            ci r1,>ff00         ; keys pressed?
            jne get_key         ; jump if something was pressed
            ai r0,>0100         ; otherwise select next keyboard column
            ai r2,8             ; next column in ascii table
            ci r0,>0600         ; done all columns?
            jne next_column     ; if not then do next column
            
        ; no key detected - check = space and enter (todo)
            li r1,>0100         ; check =
            czc r1,r6           ; compare with column 0 status
            jne check_space
            li r0,'='           ; load ascii code
            jmp check_fctn
check_space sla r1,1            ; check space
            czc r1,r6
            jne check_enter
            li r0,' '           ; load ascii code
            rt
check_enter sla r1,1
            czc r1,r6           ; check enter
            jne no_keys
            li r0,13            ; ascii for ENTER
            rt
check_fctn  li r1,>1000         ; key mask for fctn
            czc r1,r6           ; pressed?
            jne check_shift
            li r0,5             ; code for fctn/=
            rt
check_shift sla r1,1            ; point to shift
            czc r1,r6           ; check
            jne exit_rtn
            li r0,'+'           ; otherwise load +
exit_rtn    rt
            
        ; no key detected at all. push -1 for ascii code
no_keys     seto r0             ; load -1
            rt
            
        ; a key was detected on the current row. look it up...
get_key     li r0,>0100         ; key mask
next_key    czc r0,r1           ; check key
            jeq found_key
            sla r0,1            ; otherwise adjust mask
            inc r2              ; adjust ascii table index
            jmp next_key        ; repeat
             
        ; found the key. table offset is in r2
found_key   li r1,tables        ; point to data table pointers
            li r0,>1000         ; start the check with fctn
found_key1  czc r0,r6           ; pressed?
            jeq get_table
            inct r1             ; point to next table 
            sla r0,1            ; check next key
            ci r0,>8000         ; checked them all?
            jne found_key1
            
get_table   mov *r1,r1          ; get table address
            a r1,r2             ; apply offset
            movb *r2,r0         ; get the key code
            srl r0,8            ; move to low byte
            ci r1,key_shift     ; are we already pointing to the shifted table?
            jeq exit_rtn        ; if yes then don't waste time testing a-lock
            ci r0,'a'           ; check if in lower case range
            jlt exit_rtn
            ci r0,'z'
            jgt exit_rtn
        ; the character is in the lower case range. we need to check alpha lock
            clr r12             ; cru root address
            sbz >15             ; power up the alpha lock circuit
            tb 7                ; read output from circuit
            sbo >15             ; power down the circuit
            jeq exit_rtn        ; alpha lock not engaged
            ai r0,-32           ; engaged. convert to upper case
            rt
            
tables      data key_fctn, key_shift, key_ctrl, key_normal
 
key_normal
            byte '=',' ',13 , 0 , 0 , 0 , 0 , 0
            byte '.','l','o','9','2','s','w','x'
            byte ',','k','i','8','3','d','e','c'
            byte 'm','j','u','7','4','f','r','v'
            byte 'n','h','y','6','5','g','t','b'
            byte '/',';','p','0','1','a','q','z'
        
key_shift
            byte '+',' ',013,>FF,>FF,>FF,>FF,>FF
            byte '>','L','O','(','@','S','W','X'
            byte '<','K','I','*','#','D','E','C'
            byte 'M','J','U','&','$','F','R','V'
            byte 'N','H','Y','^','%','G','T','B'
            byte '-',':','P',')','!','A','Q','Z'
        
key_fctn
            byte '=',' ',13 , 0 , 0 , 0 , 0 , 0
            byte 185,194,39,15,4,8,126,10
            byte 184,193,63,6,7,9,11,96
            byte 195,192,95,1,2,123,91,127
            byte 196,191,198,12,14,125,93,190
            byte 186,189,34,188,3,124,197,92
        
key_ctrl
            byte 29,' ',13,>FF,>FF,>FF,>FF,>FF
            byte 27,12,15,31,24,19,23,24
            byte 0,11,9,30,25,4,5,3
            byte 13,10,21,29,26,6,18,22
            byte 14,8,25,28,27,7,20,2
            byte 27,28,16,22,23,1,17,26
 
            end
  • Like 3
Link to comment
Share on other sites

 

Wow! Really nice! :thumbsup:

 

Here's a fast keyboard scanning routine. The interesting stuff starts at the label key_scan

 

Thanks Willsy :) If you're interested I'll post the Mass Transfer/TIMXT version of the standalone keyboard scan for comparison. (I didn't write it)

 

I've always wondered if the standalone utilities like DSRLNK, KSCAN, etc. were ripped out of the EA support, as most of them look nearly identical, or if back then the assembly programmers shared early "libraries" and source that was used commonly. Come to think of it, I don't recall where my first iterations came from. :-o

  • Like 2
Link to comment
Share on other sites

  • 4 weeks later...

Even if internet wasn't available to share all human knowledge, there were still information sent around. Samples from the E/A manual were of course used first and foremost. Then somebody came up with better code, and you started using that, as soon as you knew about it. Myself, for example, started with the normal DSRLNK, as suggested in the E/A manual, then went with a better one, which could also handle cassette access within the same routine.

Link to comment
Share on other sites

  • 1 month later...

Recently I got my F18A system back in working order. I was playing around with TIMXT and noticed that during the display of a 38.4K stream, an occasional character was dropped. At first I thought the keyboard routine was delaying reception, so I disabled keyboard input during the display stream. No change.

 

Later I added a few NOPs to the RS232 interrupt handler. I wanted to understand whether or not the interrupt itself was taking too long. I discovered that just five NOPs was enough to disrupt character reception 30-40%. It seems 38.4K may be right at the threshold for what is possible.

 

I have mitigated the problem by tightening up the interrupt code. I don't have sufficient PAD RAM space for the routine, though I might be able to shove a few VDP read/write routines there to minimize non-interrupt time within the interpreter.

  • Like 3
Link to comment
Share on other sites

  • 1 month later...

TIMXT requires quite a bit of processing time to capture and interpret the incoming data stream. The system is often so busy with the data that keyboard scanning is inhibited, lest one or more characters get dropped.

 

Up until now, I had been allowing interrupts to steal control from the keyboard routine after each column is scanned (LIMI 2/LIMI 0). This caused some debounce and response problems depending on the current input stream, mostly due to the residual ROM ISR code overhead. grrr.

 

I tried a few different things then settled on a polled interrupt approach within the key scan routine:

1. All interrupt processing is disabled (LIMI 0).

2. RS232 interrupts are enabled.

3. The RS232 interrupt bit is polled in-line, just prior to scanning each keyboard column and during some keyboard post-processing.

4. If an RS232 character is received, it is copied into a receive buffer, and control is quickly returned to continue the key scan.

 

Overall keyboard responsiveness has improved and at 19.2K, everything responds as it should. Now I can return to playing with the RS232 interrupt handler and the nanoPEB-induced problem, maybe even re-stabilizing 38.4K in the process. ;)

  • Like 5
Link to comment
Share on other sites

Sometimes we forget to consider simple, obvious solutions to problems.

 

The old key scan routine used a workspace located in the slow 32K memory space. I only just noticed this tonight; I changed the workspace to operate out of scratchpad and was "rewarded" with a faster key scan that returned throughput to 38.4Kbps. My next steps are (1) to re-enable limited keyboard scanning during display and (2) to test some SAMS buffering routines. A 4K buffer is filled VERY quickly at 38.4K. ;)

  • Like 3
Link to comment
Share on other sites

  • 3 months later...

 

Yesterday I finally had a chance to copy my Geneve development platter (backup!) and export the RS232 routines related to this post.

 

There are some things I still plan to do to improve performance, such as code optimization and use of SAMS or 8K Superspace for incoming buffers. Since those future plans don't change the concept, I updated a few of the comments and am posting here for those interested.

*--------------------------------------------------------
* RS232 Circular Buffer using binary wrap method
​* no jumps required

BUFFER BSS >100               256 byte buffer for example
       
       MOV  @RLAST,R4         get current ^loc pointer into R4
       MOVB R3,*R4+           stuff char into buffer, auto inc. puts you at next location ready for next interrupt
       ANDI R4 >00FF          wrap R4 by ANDing with a mask. 
       MOV  R4,@RLAST         save next buffer loc
       INC  @RBYTES           inc total bytes in buffer etc...

I did a lot of work with RS 232 interrupt handlers on the PC and I found it really sped things up to set the buffer

size to a power of 2 and let the pointer wrap by ANDing it with a mask.

 

It worked well in both Assembler and was very effective in Turbo C.

 

I don't understand you code well, but I edited it without testing to show what I mean.

 

BF

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

I did a lot of work with RS 232 interrupt handlers on the PC and I found it really sped things up to set the buffer

size to a power of 2 and let the pointer wrap by ANDing it with a mask.

 

It worked well in both Assembler and was very effective in Turbo C.

 

I don't understand you code well, but I edited it without testing to show what I mean.

 

BF

Thank you, great idea! In the current code, the buffer pointer starts at 0x3000 and increases to 0x3ff0, then wraps back around to 0x3000. If I shift the buffer space from 0x2000 to 0x2fff, I can mask the pointer with 0x2fff without spilling into another 4k segment. (masking 0x3fff would allow the buffer to wrap back to 0x1000). Yes, I will need to give this a try. :)

Link to comment
Share on other sites

Also, if your Interrupt routine uses it's own workspace, you don't need to keep variables since you can use the local registers to hold the queue pointers, no?

(which are just memory that the machine can get at easier anyway ie: faster variables)

 

It would be even faster again.

*--------------------------------------------------------
* RS232 Circular Buffer using binary wrap method
​* no jumps required

* example register usage
*  R3 tail pointer            next byte to read
*  R4 head pointer            next available input location
*  R5 byte counter

INTWSP BSS >20
BUFFER BSS >100               256 byte buffer for example

* interrupt handler for the queueing the characters     
       MOVB R3,*R4+           stuff char into buffer, auto inc. puts you at next location ready for next interrupt
       ANDI R4 >00FF          wrap R4 by ANDing with a mask. 
       INC  R5                inc total bytes in buffer
       RTWP


In some applications with a big enough buffer, you can "live on the edge" and not even deal with overflow in the ISR.

It just wraps around and it's up to your reader program to keep up! :-)

 

However that only works if the data comes in bursts.

(Or the reader program hits the handshake line to stop the sending until it catches up)

 

BF

 

And of course reading characters out becomes faster as well with the wrap mask method.

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

It's a bit trickier than that, I'm afraid, but I like what you suggested!

 

To service the RS232 interrupt more quickly, the console ROM video/scanline interrupt routine is "hijacked" to pass the RS232 external interrupts through to the user interrupt vector. The active RS232 is then polled (since we know which port we are using) for an interrupt and if found, the byte is read from the 9902 receive buffer. There is still excess overhead from the ROM routine, so to keep up with a 38.4K stream a fast workspace is required. The only memory we have that fits the bill is in scratchpad RAM from 8300-83ff. In TIMXT I am re-using/leveraging the fast interrupt workspace at 0x83C0. I haven't investigated whether or not there are any registers that go untouched that could be dedicated to head/tail/counter.

 

Your comments lead me to consider additional options, which include sharing a register or two with the main workspace. This would result in what some might consider 'sloppy' code but it would have the benefit of ensuring the fastest interrupt handling and extraction. The wrap method would then be usable for the character extraction. Hmm. the keyboard handler would need to be modified, since I am pre-empting the interrupt routine through direct polling of the RS232; otherwise we lose characters during the time it takes to scan for a key.

 

My current focus has been on improving the interpreter's processing speed. It is not able to process and display the incoming data quickly enough to "live on the edge". But we're getting there. And now I have some more good ideas to think about :)

  • Like 1
Link to comment
Share on other sites

I feel your pain. LOL. You have way more patience than I do.

 

Something to consider is that there might be enough reduction in instruction count with the wrap method

​and using workspace registers, that the speed-up matches the benefit of being in 8300 memory space.

PAD memory is faster but running 2x as many instructions, with variables in slow ram, could end up being the same.

 

Maybe someone with more experience could weigh in on this.

 

It is possible to test some code fragments in Forth pretty quickly test for speed.

 

BF

Link to comment
Share on other sites

Hard to say, Willsy. I think changing the workspace pointer may be the best option - it is quick and simple. I'm already doing this in the DSRLNK code and keyboard routine ;) Thing is, I didn't write the majority of the program so with retrofitting comes not breaking things. ;)

 

Something to consider is that there might be enough reduction in instruction count with the wrap method

​and using workspace registers, that the speed-up matches the benefit of being in 8300 memory space.

PAD memory is faster but running 2x as many instructions, with variables in slow ram, could end up being the same.

Quite possible. For what its worth, before slimming down what little code I am using in the interrupt handler, the process was already operating in the danger zone. Adding two simple instructions for nanoPEB support caused enough of a delay to miss periodic characters. I had to make a few assumptions to eliminate instructions to get back those lost clock cycles. Your wrap idea probably would have saved me that trouble. Still, even if we eliminate the entire RS232 handler routine, there are still many instructions in ROM that we cannot eliminate.

 

Had TI placed the interrupt vectors in RAM, this discussion would be a moot point. We could eliminate all the extraneous code and focus solely on the RS232 interrupt. Ah well, there are many "if only" laments we must overcome with the TI. ;)

  • Like 1
Link to comment
Share on other sites

This is of course one reason for that I, when I made my 32 K RAM internal 16-bit memory expansion also allowed it to overlap all other memory in the machine. Thus I can copy console ROM to RAM, switch to RAM and change the vectors as I like.

Had TI placed the interrupt vectors in RAM, this discussion would be a moot point. We could eliminate all the extraneous code and focus solely on the RS232 interrupt. Ah well, there are many "if only" laments we must overcome with the TI. ;)

  • Like 1
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...