Jump to content
IGNORED

Interrupt Service Routine and the RS232


Recommended Posts

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.

Questions...

When you did this how did you make the memory map dynamic ? Rom was hard wired as well as $4000 and $6000 so I would think that there was major butchery on the mobo but perhaps there was some trick that worked?

 

How did you keep the ports intact?

Link to comment
Share on other sites

Now I don't have the schematics in front of me, but the principle is to fiddle with the chip select lines. Since all memory is connected to the same data bus, the chips that actually are active are selected. The other chips go to tri-state output level.

What I did was to add to the chip select decoding, to involve also a CRU bit for each bank. At power up, chip select goes to ROM, expansion cards, cartridge memory, ports and the new internal 16-bit wide memory expansion.

But setting the appropriate CRU bit, I can modify chip select to go to new RAM instead of ROM, cartridge space etc. Setting an appropriate bit also disables the new 16-bit wide RAM expansion and instead brings in the standard, 8-bit wide memory expansion, if available.

Thus I have two 32 K RAM expansions in the machine at the same time, one fast 16-bit internal for normal use and one slower 8-bit wide, that can be used for buffers in assembly programs, emulate a RAM disk or whatever you need. Or you can load a program there, if it relies on timing with normal memory to run properly.

 

Since the TI frequently use writing to ROM areas for memory mapped ports, bank switching and similar, I didn't implement write through, though. To efficiently copy ROM to RAM, I can do like this:

  • Enable RAM at 4000H.
  • Copy 8 K from 0000H to 4000H.
  • Enable RAM at 0000H.
  • Copy 8 K from 4000H to 0000H.
  • Disable RAM at 4000H (to free up DSR access again).

Now the operating system at 0000H is in RAM, and can be modified as you like.

 

I also added some hardware which causes a one cylce wait state when accessing the VDP. That too is controlled with a CRU bit, since it's not needed for software which does have that extra NOP which is recommended by TI. But in some cases, with normal memory expansion, such a NOP isn't needed. Thus some programs don't have it, but then fails when you run from faster memory.

  • Like 2
Link to comment
Share on other sites

I doubt I have a cut list for the traces, but I do have some schematics, somewhere.

Meanwhile, the last three pictures in this album shows the internal modification. Only the last one also contains the VDP access wait state logic.

As you can see I never made any PCB, but the modification is completely self-contained inside the original metallic screen inside the 99/4A. Thus you could for example run assembly programs without the PEB, if you loaded from cassette. Like you could with Mini Memory, but here you could also have Extended BASIC plugged in, for example. I did a demo of Forth on a seemingly unexpanded console, with cassette tape recorder connected only, at a meeting in Sweden once.

 

To begin with, I installed the modification with one single bit toggling all 32 K RAM expansion, and individual bits toggling the other 8 K sections. But I realized that it was smarter to be able to handle the 8 K RAM and the 24 K RAM expansion areas (2000H-3FFFH and A000H-FFFFH) separately, so I modified the logic to Control them by two different bits. Since I also need a bit for the VDP wait state generation control, and I have eight bits in my latch, I couldn't spend one bit per 8 K memory bank. But usually it makes sense to consider the 24 K RAM part as one single section anyway.

 

Forgive me if I have made some error. I did this modification in the 1980's.

 

Together with the 56 K Maximem universal module, this additional 64 K RAM memory and some memory on my additional home made wire wrap PEB cards, I simulate a RAM-disk for the p-system. I remember that compiling a Pascal program was done in about half the time when the compiler was in RAM all the time, due to the extensive segmentation of that program.

Later additional memory devices with a lot more memory has been designed, but 176 K RAM was quite a lot in 1985. The fact that my design co-exists with the standard 32 K RAM expansion made a difference.

I used 400H as the base address for the CRU bits used to swap in/out RAM segments.

Edited by apersson850
Link to comment
Share on other sites

*** WARNING WARNING WARNING ***

VIEWERS ARE HEREBY ADVISED THAT THE ABOVE LINK FEATURES PHOTOS OF A SILVER TI-99/4A CONSOLE FITTED WITH A BEIGE KEYBOARD. YES YOU READ THAT RIGHT FOLKS. VIEWER DISCRETION IS ADVISED. THIS WEBSITE SHALL NOT BE HELD RESPONSIBLE FOR FEELINGS OF OUTRAGE, SORROW, SHOCK OR DISTRESS.

YOU HAVE BEEN WARNED.

 

We now return you to your normal content.

 

Thank you.

  • Like 2
Link to comment
Share on other sites

Even though I haven't done much to improve the interpreter, TIMXT now seems to be stable at 57.6K with the wired connections shown in the screenshot. ;)

 

Did you incorporate the power of 2 sized buffer concept to get that the speed?

Link to comment
Share on other sites

 

Did you incorporate the power of 2 sized buffer concept to get that the speed?

In this case, no. I was being coy, trying not to say too much because what is shown in the picture is TIMXT running from the ubergrom cartridge UART. Unlike the 9902-based RS232, the UART has a multiple-byte FIFO, which makes incoming data capture a bit simpler. I still plan to implement the power of 2 buffer in both versions, as soon as I have the time to do so. :)

  • Like 2
Link to comment
Share on other sites

In this case, no. I was being coy, trying not to say too much because what is shown in the picture is TIMXT running from the ubergrom cartridge UART. Unlike the 9902-based RS232, the UART has a multiple-byte FIFO, which makes incoming data capture a bit simpler. I still plan to implement the power of 2 buffer in both versions, as soon as I have the time to do so. :)

 

Ahh.... hardware FIFOs are the bomb. Make serial communication sooo much easier. Almost like cheating to us old-timers.

 

Thanks for the update

 

B

Link to comment
Share on other sites

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

**********************************************************
* 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
* eof

 

First off, this code is so very helpful. I don't fully understand the interrupt stealing details yet but I am glad somebody does.

 

I think I have a smaller GET2 routine for you.

 

Excuse the use of Forth Assembler. TOS is just an alias for R4 and W is an alias for R8.

Use the registers that work for your program of course. I put ASM translations in some comments.

 

QTAIL is your RLAST renamed.

 

The strategy is to use indexed addressing and a binary wrapped index variable to create a circular queue.

It does not count the bytes in the queue, because you can get that by subtracting the values in QHEAD and QTAIL.

There is no protection for overflows. The data will just over write old data, but it is small so it should be fast.

This is as small as I can get it.

HEX
                   ASM Translation of Forth
VARIABLE QHEAD         \ QHEAD DATA 0
VARIABLE QTAIL         \ QTAIL DATA 0

CREATE Q  100 ALLOT    \  Q   BSS 100  

CODE GET2   QTAIL @@ W MOV,   \ qtail -> W  (ie: R8 )
            Q (W) 8 STCR,     \ read char into Q@(W)
            12 SBO,           \ enable interrupts, clr Rcv buffer flag
            W INC,            \ bump the index
            W 00FF ANDI,      \ wrap the index
            W QTAIL @@ MOV,   \ save the index

Edit: attempt at using TI Assembly language and your code

Q      BSS  100
GET2   MOV  @RLAST,R4         get current ^loc index
       STCR Q(R4),8           get character from RS232 (R12 set @manip3)
       SBO  >12
       INC  R4
       ANDI R4,00FF
       MOV  R4,@RLAST         save next buffer INDEX
       CLR  R12
*      NOP                    debug
       RTWP

 

Edited by TheBF
Link to comment
Share on other sites

You do realize, of course, that there is a fix for the interrupt handling routine for the RS232. It works on standard and V9938-modified consoles.

 

No I don't know about that. I have all standard equipment so probably my options are limited.

I will look into what a V9938 modification entails.

 

Edit: the fix for "standard consoles". Is it the code posted earlier here by insanemultitasker?

Edited by TheBF
Link to comment
Share on other sites

So these new EPROMS are better for using the card's DSR routines I gather.

 

I am talking to the 9902 directly which is very simple to do and is working nicely.

But of course for receiving I need interrupts to make data rates faster. Not sure if these eproms will improve things for a direct CRU driver.

 

I am referencing these comments below from Insanemultitasker in the code posted earlier here.

38.4K is way faster than I would ever need.

 

My earlier comment was that I think I simplified the routine that grabs a character and puts in the receive queue, which means it can go faster, or give more time back to the main program.

*      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. 
Link to comment
Share on other sites

You do realize, of course, that there is a fix for the interrupt handling routine for the RS232. It works on standard and V9938-modified consoles.

 

It is important to note that the EPROM fixes to the interrupt handling and the dual RS232 issue will not improve the overall interrupt reception speed, which tops out around 4800 on a good day.

 

The trick that Jeff Brown came up with (and that I implemented) removes the RS232's EPROM-based handler from the equation to dramatically y improve reception speed.

 

 

So these new EPROMS are better for using the card's DSR routines I gather.

 

I am talking to the 9902 directly which is very simple to do and is working nicely.

But of course for receiving I need interrupts to make data rates faster. Not sure if these eproms will improve things for a direct CRU driver.

 

Correct. The replacement EPROMS correct issues and allow you to open the ports at higher speeds. However, you won't get any better reception speeds for sustained data transfers - the EPROM-based interrupt routine coupled with the steps needed for the TI's ROM interrupt service routine to find and call it, are just too slow.

Link to comment
Share on other sites

The bottle neck appears to be the coding of the terminal program and not necessarily the RS232 DSR (which can be tweaked to allow higher data transfer rates). For example, an unmodified RS232 will allow transfers of up to 19.2K baud but is limited by the terminal program code.

 

I sent a bunch of "beta" DSRs to Richard Bell many years ago that performed satisfactorily at 38.4K baud with some programs but not with others.

Link to comment
Share on other sites

The bottle neck appears to be the coding of the terminal program and not necessarily the RS232 DSR (which can be tweaked to allow higher data transfer rates). For example, an unmodified RS232 will allow transfers of up to 19.2K baud but is limited by the terminal program code.

 

I sent a bunch of "beta" DSRs to Richard Bell many years ago that performed satisfactorily at 38.4K baud with some programs but not with others.

 

Good to know. Truth be told, interfacing to cooperative multi-tasker requires that I/O routines are well behaved so using DSRs for primary user interface device (which is what I built) is probably not a good idea.

 

So I will persevere for now on trying to make Jeff and "IMT's" interrupt code run under Forth. I did a translation and it compiles but I need to built a little test bench to see if the interrupt is actually stealing characters or just crashing. :)

 

I think I will test with TI-99 console as primary to get the darn thing running. Testing over the rs232 port that is my terminal is a little ambitious!.

 

Thanks all for chiming in.

Link to comment
Share on other sites

The bottle neck appears to be the coding of the terminal program and not necessarily the RS232 DSR (which can be tweaked to allow higher data transfer rates). For example, an unmodified RS232 will allow transfers of up to 19.2K baud but is limited by the terminal program code.

 

I sent a bunch of "beta" DSRs to Richard Bell many years ago that performed satisfactorily at 38.4K baud with some programs but not with others.

 

Programs that use the RS232 interrupt routine will suffer data loss when the data stream is sustained and/or beyond a certain character per second threshold. The programs that worked satisfactorily at 38.4K were either using direct polling or were not being hit with data at a "high", sustained rate, typically 9600 or above.

 

I admit I may be missing something here, but if the tweaking you refer to is adjusting the baud rate table, that will not positively affect the rate a which data can successfully be received. The baud rate table changes allow you to open the port at a high speed, whether or not the routines can accept the data at that speed.

 

For an analogy, using the RS232 EPROM interrupt routine at high speeds is like trying to fill a shot glass with a fire hose. Sure, you can do it, but you're going to lose a lot of water (data) in the process, unless you allow only a mere fraction of the fire hose's potential flow rate to trickle out. You can tell me you filled the shot glass with that fire hose but it wasn't with the valve open 100% and certainly not without making a big old mess ;)

Edited by InsaneMultitasker
  • Like 1
Link to comment
Share on other sites

I admit I may be missing something here, but if the tweaking you refer to is adjusting the baud rate table, that will not positively affect the rate a which data can successfully be received. The baud rate table changes allow you to open the port at a high speed, whether or not the routines can accept the data at that speed.

 

By tweaking I just mean making it work under Forth. I did a complete re-write in Forth Assembler so there are bugs for sure somewhere.

 

For example I am not sure what is save where when the interrupt is called and how the CPU finds its way back to wksp 8300 which is where Forth lives.

Will an RTWP inside 83C0 WKSP be enough to get me back to my workspace?

 

For reference my requirements are the ability in receive 80 chars at 9600 bps reliability. After that the terminal program will delay 250 ms while Forth digests the line.

That's all I am looking for so from what you tell me It should be possible.

 

EDIT:

 

I just did a test a 4800 bps, no interrupts and I can do the above; send a line at full speed , compile it and have Terra Term wait 250 ms. Seems good.

Forth is echoing all the characters back to the terminal so in theory... if I remove echo during sending I could do 9600.

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

 

I just did a test a 4800 bps, no interrupts and I can do the above; send a line at full speed , compile it and have Terra Term wait 250 ms. Seems good.

Forth is echoing all the characters back to the terminal so in theory... if I remove echo during sending I could do 9600.

Yep, you can poll in a loop and get 9600 and faster quite easily without any interrupts or interrupt handler. This method works well for packet data or anything that doesn't require a lot of interaction; I do this with my BBS software during file transfers. Be aware that if system interrupts are enabled during the reception you may drop chars in your polling loop, and the interrupt routine might mess with your R12 value, so you might need to experiment with LIMI xx to turn ints off/on.

  • Like 1
Link to comment
Share on other sites

Just in case somebody hasn't grasped this:

The baud rate, really the number of bits per second that are transmitted, rely to one single character only. When using a baud rate of 9600, it means that the duration of a single zero or one bit is 104 us. This is valid inside a character, which typically involves transmitting eleven bits, of which eight are data. Thus the duration of one character is 1.15 ms.

The higher the baud rate, the shorter time between the bits. But it's up to the UART chip to handle this. It will receive the bits and put them together to one character. So far, it doesn't involve the processing capacity of the CPU.

 

It's when the UART has received a character the race starts. It will set an output to tell there is a character to read, and will start receiving the next one, if one more is transmitted. The character that's now ready to read must be read before the next one is assembled. If it isn't, an overrun condition will occur. This can be handled in different ways, but a typical result is that a character is missing. Or an error could be generated.

 

This means that you can have a very high baud rate, but still only send a single character every second. It doesn't make sense, but it's doable, legal and implies no significant load on the CPU at all.

What you can't do is have a slow baud rate, like 300, and send/receive many characters per second. At that baud rate, a single character takes so long to send, that you can't do more than 27 per second.

 

The benefit of using interrupts is that you can connect the "I have a character" output from the UART to the CPU's interrupt input, and have the CPU empty the single character buffer, regardless of when it happens. The CPU software doesn't have to repeatedly read the UART to see if there's something. It can do whatever it has to do, and the incoming character will be read when it occurs. The problem with this approach is that to manage high communication speeds, the interrupt service routine must be short and efficient.

You can say whatever you like about the interrupt servicing, for external interrupts coming from the expansion box, but it is neither short nor efficient.

 

That's why there's a problem with many characters per second. Not with the 38400 bits/second by itself, but the many characters per second it allows.

  • Like 1
Link to comment
Share on other sites

Just in case somebody hasn't grasped this:

The baud rate, really the number of bits per second that are transmitted, rely to one single character only. When using a baud rate of 9600, it means that the duration of a single zero or one bit is 104 us. This is valid inside a character, which typically involves transmitting eleven bits, of which eight are data. Thus the duration of one character is 1.15 ms.

The higher the baud rate, the shorter time between the bits. But it's up to the UART chip to handle this. It will receive the bits and put them together to one character. So far, it doesn't involve the processing capacity of the CPU.

 

It's when the UART has received a character the race starts. It will set an output to tell there is a character to read, and will start receiving the next one, if one more is transmitted. The character that's now ready to read must be read before the next one is assembled. If it isn't, an overrun condition will occur. This can be handled in different ways, but a typical result is that a character is missing. Or an error could be generated.

 

This means that you can have a very high baud rate, but still only send a single character every second. It doesn't make sense, but it's doable, legal and implies no significant load on the CPU at all.

What you can't do is have a slow baud rate, like 300, and send/receive many characters per second. At that baud rate, a single character takes so long to send, that you can't do more than 27 per second.

 

The benefit of using interrupts is that you can connect the "I have a character" output from the UART to the CPU's interrupt input, and have the CPU empty the single character buffer, regardless of when it happens. The CPU software doesn't have to repeatedly read the UART to see if there's something. It can do whatever it has to do, and the incoming character will be read when it occurs. The problem with this approach is that to manage high communication speeds, the interrupt service routine must be short and efficient.

You can say whatever you like about the interrupt servicing, for external interrupts coming from the expansion box, but it is neither short nor efficient.

 

That's why there's a problem with many characters per second. Not with the 38400 bits/second by itself, but the many characters per second it allows.

 

The only thing I would add for those still learning about interrupts is the "no free lunch" rule applies as well. If you send characters at 38k4 bps and your interrupt is efficient enough to catch them and put them safely in a buffer, you are still using CPU time. It is not under your direct control however. So with a fast enough data rate and under a continuous stream of data, and a slowish machine like the 9900, you may find that your computer is spending 90% it's time running the interrupt code when receiving data, leaving little CPU time for your program.

 

This is why Apersson's law must be observed: "the interrupt service routine must be short and efficient."

 

The way to know the load is to compare the time to send one character and the time it takes for your interrupt handler to put that character away in a buffer.

 

Back of a napkin...

 

  • At 38,400 bps 1 char takes about 286uS (11 bits x (1/38,400))
  • The TI-99 does 1 machine instruction in about (assuming 18 clocks/instruction) 6uS in scratchpad RAM, slower in expansion RAM.
  • So if your handler has more than ~47 instructions total, in full speed RAM, you cannot sustain full speed data streaming at 38,400 bps.
  • 47 instructions X 6uS= 282uS

 

282uS / 286uS -> 98% of CPU used by RS232 interrupt with 47 instructions in fast RAM.

 

This of course improves if use a slower baud rate because you have more time between characters.

 

Morning coffee talking

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