Jump to content
IGNORED

Pascal on the 99/4A


apersson850

Recommended Posts

Posted (edited)
On 3/9/2024 at 5:21 PM, Vorticon said:

It looks like changing the long integer definition from integer[10] to integer[4] works. I'm not clear as to why though because that index value is supposed to indicate the number of decimals that long integer can represent. I would love an explanation if anybody has one. 

It works because long integers are really binary representations of your number, but the shortest one seems to be four bytes long. I did some testing with Classic 99 to figure this out. I declared some different long integers, checked where the variable was in memory and observed it as I entered new values into it via a running Pascal program. That's how I found out.

The system's timer is a 2BA4 and 2BA6, least significant first.

Edited by apersson850
Link to comment
Share on other sites

Posted (edited)
30 minutes ago, Vorticon said:

Why I does .ORG 0A000h give me an out of bounds operand error? I should be able to locate an assembly routine in high memory, right?

Did you preceed it with .ABSOLUTE ? Otherwise it just thinks you are trying to make a program 40960 bytes big, and counting, and that may be a tad too much...

And although you can indeed place an assembly routine almost anywhere you like, there are two issues: You can't load it where the system's code loader is currently running and you can't overwrite things the system needs to function.

The code loader may very well run from the p-code card, but at A000 starts the system's heap and some global data structures. So although you may be able to load it there, it may harm your chances of returning to the p-system.

Why do you want to store your code there, of all places?

Edited by apersson850
Link to comment
Share on other sites

Posted (edited)

There's not much, really. The p-system occupies even the dark and dusty remote corners of the RAM available.

Here's a simple overview.

 

VDP RAM

0000: Screen and sprite tables, keyboard queue.

INTMEM (0DF8): Start of interpreters memory. This is the primary code pool.

TOPMEM (3EDF): Buffers for disk access and similar. The primary code pool ends here.

3FFF: End of IO buffers.

 

CPU RAM

2000: 80 column screen.

2780: Various system data and tables.

2B6E: SYSCOM area.

3580: Kernel global data.

3A00: May be free. No activities I know of take place here  <-------

3FFF: End of low RAM expansion

 

A000: Beginning of system's heap. Changes size all the time.

xxxx: Secondary code pool, end of heap.

yyyy: Top of stack, end of code pool.

FFFF: Bottom of stack

 

As you can see my best suggestion would be end of 8 K RAM expansion. I took a quick look using Classic 99 and can't see any memory activity beyond 3A00 at least. Maybe ends already at 3910.

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

5 hours ago, apersson850 said:

As you can see my best suggestion would be end of 8 K RAM expansion. I took a quick look using Classic 99 and can't see any memory activity beyond 3A00 at least. Maybe ends already at 3910.

Good to know. Thanks.

Link to comment
Share on other sites

20 hours ago, apersson850 said:

Anyway, this is the beginning of the code that runs on each VDP interrupt under the p-system. Now just like many other programs do their LIMI 2 LIMI 0 sequnce when they are ready to be interrupted, the p-system may not run the interrupt service right after the machine code instruction where it occurred, but it will run it frequently enough to serv all 50/60 of them under normal circumstances.

INTSRV	SBO	2
	MOVB	@>8802,@VDPSTCOPY
	INC	@LOWTIMER
	JNC	SKIPHI
	INC	@HIGHTIMER
SKIPHI	INCT	@SCRBLANK 

The code is at 32DC if you want to look at it. Branch vector at 2AAA so the workspace is 28B6.

I'm afraid interrupts are still very hazy topic for me. Say I want to set up my timer routine to run with VDP interrupts. What is the procedure to do so? There is a list of system interrupts in the UCSD assembler manual on pages 231-232 but it does not give any hints on how one works with them.

Link to comment
Share on other sites

6 hours ago, Vorticon said:

I'm afraid interrupts are still very hazy topic for me. Say I want to set up my timer routine to run with VDP interrupts. What is the procedure to do so? There is a list of system interrupts in the UCSD assembler manual on pages 231-232 but it does not give any hints on how one works with them.

It doesn't get any easier just by the fact that interrupts in the p-system aren't handled the same as in the normal operating system.

A "real" interrupt is something that causes the CPU to branch to an interrupt service routine once the currently executing instruction has been completed. Provided the interrupt mask allows the interrupt to happen, it will, whether you like it or not. This is what will happen if you run your own assembly code and does allow interrupts to happen.

The normal OS frequently runs the GPL interpreter. That's an emulated CPU executing GPL instructions instead of the CPU's normal instruction set. Each GPL instruction takes a number of machine instructions to execute. Just like with assembly, you want to allow interrupts between instructions, not inside them. When the GPL interpreter is running, you want to complete interpretation of the currently executing GPL instruction before being interrupted. The GPL interpreter handles this by the sequence LIMI 2 LIMI 0 at a convenient place in the code. This assures that the code is not interrupted at a place where that will make it go wrong, but in between completed GPL instructions. But once the LIMI 2 instruction has been executed, the interrupt will happen in the same way as in your assembly program.

The PME is similar to the GPL interpreter. It interprets p-code, an instruction set that's not native to the TMS 9900, just like the GPL interpreter does with GPL. And in the same way the PME doesn't want to be interrupted in the middle of interpreting a single p-code. Just like the GPL interpreter, the PME picks the time when it allows an interrupt to be executed. Different from the GPL interpreter, the p-system does never execute any LIMI 2 instruction to allow hardware interrupts. That's because the hardware interrupts execute things the p-system doesn't want to happen and it does not execute things the p-system does want to happen.

 

Both systems runs a screen blanking timer.

Only the p-system has a type ahead queue, so it wants to scan the keyboard for that.

Both systems can handle sprite auto motion, but it's more advanced in the p-system, so it wants to run it's own version.

Both systems allow sound list processing, but again it's different in the p-system.

Both systems want to be able to serve external interrupts.

 

The standard interrupt service is in ROM, so the p-system can't change that. Instead, the p-system never runs it, but instead at regular intervals check if the interrupt signals are set. This is done by testing specific CRU bits in the TMS 9901. If the appropriate bits are set, to indicate VDP or external interrupts, the corresponding action is taken by branching to the code in the PME handling the interrupt in question. This is the reason why you can't install a user defined interrupt sequence in 83C4. That thing is handled by the system's interrupt servcie, which never runs.

P-system implementations that suppport the intrinsic attach will allow you to link various system events to a semaphore, so you can do interrupt service on Pascal level. But that's it. There is no suppor for user interrupts on assembly level.

 

When I did do that in my multitasking exepriment, I tapped into the simulated interrupt service code in the p-system, branched to my multitasking routines and then branched back again.

When I implemented the real time clock in the screen all the time, I used the ability the p-system actually has to allow assembly level interrupts - I created an external interrupt. My real time clock sits at CRU base address 1400. That's no problem, since the p-system will scan every DSR it can find at boot time and create a list of cards that have interrupts service routines and execute these when they do generate an interrupt. But that's the only really supported way of adding an interrupt to the p-system in the 99/4A.

 

Now there's no point in adding a timer interrupt, since the system already runs one. But if you want to do something else you have to wedge it in like I did.

 

Now a bit off topic, but just a bit.

When I tired to implement pre-emptive multitasking, I did that by adding task switching in the interrupt service. I've later realized that it would have been better to actually make attach work. By defining some system events, like VDP interrupt and keyboard buffer not empty, it would then be possible to signal a semaphore on such events and have interrupts working on Pascal level. Of course not as fast as real interrupts, but there's assembly programming for that. But you could with relative ease implement a program that draws graphics on the screen and have another process waiting for keyboard input and do someting when that happens. It would be a clean and nice solution.

It can also be applied to create pre-emptive multitasking. By attaching a timer event to signal a semaphore which a high priority process is waiting for. Since it has high priority, it will always be given the processor. But the first thing it does is to wait for the same semaphore again, so processing goes back to the first process waiting in the ready queue. Now the nice thing here is that if you have several processes with the same priority level, the one interrupted by our task switching process will be put at the end of the ready queue by the system, so the one that waited for the longest time will run instead. On the next interrupt, this rescheduling happens again, rotating through all tasks ready to run.

 

This was the long answer to the short fact that there's no real provision for adding your own interrupts to the p-system, unless they are embedded in a DSR on an accessory card.

  • Like 6
Link to comment
Share on other sites

9 hours ago, TheBF said:

So from what I understand, would the best way to make a tight timer be to write it in Assembler and just let it control the machine while it delays?

 

I suppose one could test a specific CRU bit on the 9901, for example bit 2 for the VDP vertical sync, and increment a value with every interrupt, but as you said not much else could be done while this is running. Probably useful for exact delays but not so much for timing functions.

  • Like 1
Link to comment
Share on other sites

Posted (edited)

I've no experience from this, since I have a real time clock in my machine. I just use that one.

I've written a unit realtime, which among other things support creating any number of stopwatches/timers. So I create one, start it and then wait for it to reach the desired time on Pascal level. For a precision that's reasonable to expect from a high level language on the TI 99/4A, that's enough. It you want millisecond precision you need to go to the assembly level anyway.

When I don't need the timer any longer I dispose it in the program and the memory it occupied is released.

 

But I do know that Pascal's time function works pretty well as long as you don't do disk access or other stuff that prevents interrupts from being serviced.

 

After inspecting the memory with Classic 99 I realize that my multitasking support could probably have lived in the higher part of 8 K RAM. But back in the days it wasn't easy to verify if memory there was never used or just rarely. I would have had to fill the area with a test pattern, run the system doing different things and then verify if the test pattern was still there to get an idea. So I simply plugged in the Mini Memory to get a RAM area I was certain the p-system didn't use. That's where I stuffed my multitasking support.

 

For controlling that elevator I wrote about I implemented a specialized version of the attach intrinsic. On VDP interrupt I branched out to my own interrupt service. There were two things it needed to check:

  • Buttons used to request the elevator to go to a certain level.
  • Level indicators showing it had reached a certain level.

The interrupt service checked these sensors on assembly level. If any was active, it would signal a semaphore on Pascal level. In Pascal, I then had a process which handled requests for different levels (entered them into a queue) and another process which checked if the last reported level was the one it was heading for. If so the brake was applied and the motor stopped. If not, it just let the elevator continue.

When the processes had done their task, they started waiting on their semaphore again and the idle task would run. In a later stage some visualization of the elevator was running instead.

 

This worked fine with the multitasking support in Pascal. It implemented a real-time control system on Pascal level, just supported with the interrupt service in assembly.

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

I was just reading last night about concurrency in UCSD Pascal and it's a really powerful feature, particularly for interfacing like you did. I have plans to wirelessly interface my Heathkit Hero Jr robot to the TI and control it with UCSD Pascal. I've already done that in XB but it's slow and clunky at best. I can do better with Pascal and concurrency.

 

 

2 hours ago, apersson850 said:

For controlling that elevator I wrote about I implemented a specialized version of the attach intrinsic.

The manual says it is not implemented on the TI. How did you modify that?

  • Like 2
Link to comment
Share on other sites

On 3/9/2024 at 1:44 PM, apersson850 said:
time(high,low);
long1 := high;
long2 := low.
if long1<0 then long1 := long1+65536;
if long2<0 then long2 := long2+65536;
longtime := long1*65536+long2;

So your suggestion above results in exactly the same results as what I implemented below. 

 

 longint = record
  case boolean of
   true : (value : integer[4]);
   false : (intval : packed array[0..1] of integer);
  end;

 

time(stime.intval[0], stime.intval[1]); {start time}
   repeat
    time(itime.intval[0], itime.intval[1]) {interval time}
   until (itime.value - stime.value) > 70; {40 second timeout limit}

 

So I'm assuming that the longint variant record I created is valid, at least for integer[4]. 

  • Like 1
Link to comment
Share on other sites

Posted (edited)

Yes, my assumption was wrong when I wrote that proposal. It works, but is not necessary, since the long integer is a true binary representation of an integer. Just longer. 😃

But the number of bytes the system allocates to a long integer are more than really necessary. I did not test all variants, but I saw indications (not proof) that it allocates four bytes at a time, so a number of declared lengths get four bytes, then it jumps to eight and twelve and so on. I'm not sure, but I got one twelve bytes long in spite of asking for something that would fit in nine. Perhaps makes their algorithms easier to work with blocks of four bytes.

 

Regarding attach, I didn't change the implementation of attach (it literally does nothing at all), but instead wrote an assembly program which got a pointer to a semaphore, so it knew where in memory that variable was located. That assembly program was linked into the VDP interrupt service by simply branching out if it to a new piece of code and then branch back again. So at each VDP tick my added interrupt routine checked the inputs on the parallel port and modified the semaphore it knew the location of. This is exactly what attach would accomplish, had it been defined in the system. Still, such an interrupt would not have been prepared, since I designed that hardware myself, but there is no difference in principle at all.

 

In systems where attach does work, there are some hardware events defined. Say that event 16 is something you want to use. In such a case you declare a semaphore and link it to event 16 by using the attach intrinsic. Once you've done that, the linked semaphore will be signalled as soon as the event occurs, whatever it is. A timer tick or something else.

It would become a cleaner design if I define a number of such system events and modify the attach p-code to be able to link these events to semaphores. Then not so much special code has to be written to handle such interrupts. Since I have a hardware clock I could define events every second, minute, hour, day etc., in addition to key pressed or external hardware things. My TI also has an IO-board with 40 inputs and 32 outputs, plus 8 analog input channels, so I have other hardware that could define interrupts too.

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

Posted (edited)

Without knowing for sure, I've understood that it was pretty common that various UCSD p-system installations did not have a working attach. Those that did seems to have been a minority.

 

For those unfamiliar with the p-system's concurrency support, let me do a quick summary.

From version IV.0 there is support for concurrency. This is implemented with the intrinsics process, start and semaphore.

You declare a process almost like a procedure. But you don't call it when needed, like you do with a procedure, but start it with the start keyword.

Once started, the process will run in parallel to your main program. But since the p-system runs with volountariy task switching only, no task will stop to let another one run unless it does it by itself. You can start a process which then runs until it waits for a semaphore. When it hits the semaphore it will stop there and not return to a running state until that semaphore is signaled. Whoever signals that semaphore will be stopped, provided the waiting process has higher priority. If it has lower priority, it will still not run, even though the semaphore isn't blocking it any longer.

 

This works, with a careful layout of your program. But the main benefit of this kind of multiprogramming is if there's no real main program. Instead you have tasks doing whatever needs to be done only when it needs to be done. The rest of the time no useful task is running. For the elevator control I once did you can imagine these tasks:

 

A button task, which signals when a button is pressed and feeds the requested level into a queue.

A level task, which signals when a certain level is reached.

A running task, which controls the motor and sends the elevator cage to the next level in the queue, if there is any waiting in the queue.

A stopping task, which stops the motor is there's a signal about that the level reached is the same as the current destination. This will have a higher priority than running.

An idle task, which runs when there's nothing else to do.

 

On the TI 99/4A you run into the problem that the button and level tasks need to run simultaneously, since you can't know which one needs to be invoked first. The best thing there would be to be able to connect them to a hardware event. That can be done with attach, when there's a working implmentation. Since there isn't on the 99/4A, I simulated that by running an assembly routine which was invoked on the VDP interrupt, checked the switches and then signaled the appropriate button or level. Then they in turn signaled the other tasks.

 

My attempt to implement multitasking in a pre-emptive fashion was based on checking the ready queue at a regular interval. If there was something ready to run with same or higher priority than the currently executing task, I would switch task, in spite of the fact that the running one hadn't given up voluntarily.

This almost worked, but it turned out that some operations in the operating system's units heapops or extraheap didn't work with this feature active. I've later realized that I should have done this by making attach work instead. That would have been a better approach that perhaps would have worked in all cases. I just didn't think about it back then.

The intrinsic attach is compiled to a call to some assembly code that implements it. It's the same as for things like moveleft or unitread. It's not a p-code as I first claimed - only signal and wait are unique p-codes in the context of concurrency. I have to check my documentation if you can change attach too. If not, we simply make a ti_attach instead. So it's still not an issue.

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

2 hours ago, apersson850 said:

The intrinsic attach is compiled to the p-code attach, so there's a direct link. Since it's easy to change what a p-code does, it's no issue to modify attach to do something useful instead of being a no-operation as it is today.

Easy for whom? :lol:

That said, I am willing to volunteer to get it to work if you guide me through the process. It would be a great learning experience. 

2 hours ago, apersson850 said:

I simulated that by running an assembly routine which was invoked on the VDP interrupt, checked the switches and then signaled the appropriate button or level. Then they in turn signaled the other tasks.

Is this the part where you "wedged" your code into the system code activated by the VDP interrupt (CRU bit 2 of the 9901)? How would one go about doing that? 

Link to comment
Share on other sites

Posted (edited)
7 hours ago, Vorticon said:

Easy for whom? :lol:

That said, I am willing to volunteer to get it to work if you guide me through the process. It would be a great learning experience. 

Is this the part where you "wedged" your code into the system code activated by the VDP interrupt (CRU bit 2 of the 9901)? How would one go about doing that? 

Knowledge is easy to bear. It's just hard to get!

I was wrong when I thought attach is a p-code. Signal and wait are, but attach is instead an intrinsic like moveleft or unitread. So we can simply write our own attach (ti_attach) if we like.

Then the "wedging" comes in where we implement the support for attach. The principle for that is illustrated here:

; Code handling interrupts before wedge is inserted

.
.
.
INCPLACE	INCT @something
.
.
.
-----------------------------------------------------
; Interrupt code after installing wedge

.
.
.
INCPLACE	B	@WEDGE
.
.
.

;Somewhere else, like high up in 8 K RAM or in Mini Memory

WEDGE	INCT @something	; The instruction we replaced with the wedge
.
; Whatever we want to do more
.
	B	@INCPLACE+4	; Return to right after where we put the wedge

 

That's how I inserted my multitask support. Took out an instruction, branched to Mini Memory, put the stolen instruction there and the other things I wanted to do. In the case of attach we have to define the events (interrupt sources) we want to be able to link as a table and insert addresses to the desired semaphore in a table. If the value is zero there is no link. But if there is, the semaphore pointed to is signaled when the event occurs. Doing that is not as simple as just changing a value from 0 to 1, but the task(s) waiting for the semaphore must also be moved to the ready queue. This is done by manipulating the TIB (Task Information Block) for each task that's applicable. How to do that I figured out when I made my experiments with multitasking.

If then a Pascal process is waiting for that semaphore, it's like you get an interrupt up at Pascal level. It will of course not be as fast as assembly can do, but it will be equally automatic and convenient.

 

The attach procedure call should establish the link and this run-time support execute it.

 

I realized I should add that the ease of modifying p-codes and intrinsic procedures comes with the fact that they have address tables in RAM. So you just write the new code you want them to have and change the pointer in the address table and voila, you have a new functionality.

Attach takes a semaphore pointer and an event number as arguments, so the current implementation is to pop two words from the stack and return.

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

Very clever framework indeed.

As a simple test, let's say I want to run my little timer code with the VDP vertical scan from Mini Memory (Eventually I want to us the SAMS card for such projects but I still have to develop some access utilities for it in the pcode environment).

How do I go about finding out which native code is currently being run when CRU bit 2 of the 9901 is activated and where is that code currently living?

Link to comment
Share on other sites

Posted (edited)

Well, you can do as I did, print a disassembly when the p-system is running (first I had to write a disassembler in Pascal, of course) and then read that carefully to figure out what's going on.

Or you can start up Classic 99 with the p-code "cartridge" enabled, set a breakpoint at 53E4 and start following what's going on. At 32E4 you'll see the least significant word of the system time being incremented. It will also move around data to be able to call the keyscan for the keyboard buffer here. It's called at 42F6. Moving around data is necessary as it uses the console's KEYSCAN but normally has other data where KEYSCAN expects them.

 

You'll also notice that there's a lot of branching back and forth. The PME fetch routine is at 8300. If you set a breakpoint there, and only there, you'll notice that as long as the system spends its days waiting for you to press a key to give a command, no p-codes are executed.

Edited by apersson850
  • Thanks 1
Link to comment
Share on other sites

OK here's XMODEM. I've implemented the simplest version, namely XMODEM CHECKSUM. Attached is also a disk image with all the RS232 utilities I've developed.

File sending works at most baud rates, but file receive errors out above 1200 bps. Still plenty fast for TI files.

@Rhodanaj is looking into the possibility of allowing Pcode Tool to insert transferred files into existing disk images.

 

Spoiler
{$i-}
(* xmodem file transfer utility *)
(* xmodem checksum only. xmodem crc not supported *)
(* by walid maalouli *)
(* march 2024 *)

program xmodem;
label
 1;

type
 byte = 0..255;

 byteword = record
  case boolean of
   true : (value : integer);
   false : (bytes : packed array[0..1] of byte);
  end;
  
 longint = record
  case boolean of
   true : (value : integer[4]);
   false : (intval : packed array[0..1] of integer);
  end;

var
 key, d : integer;
 send, timeout : boolean;
 fname : string;
 tfile : file;

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

procedure sendfile;
label
 1;

var
 i, j, flag, blocksin, buffloc, packet, tdata : integer;
 comcode: byteword;
 stime, itime : longint;
 block : array[0..131] of integer;
 buffer : array[0..255] of byteword;

begin (* sendfile *)
 packet := 1;
 buffloc := 0;
 while (not(eof(tfile))) or ((eof(tfile)) and (buffloc < 255) and
       (buffer[buffloc].bytes[0] <> 26)) do
  begin
   if buffloc = 0 then
    begin
     fillchar(buffer, 512, chr(26)); {fill buffer with padding character}
     blocksin := blockread(tfile, buffer, 1);
     buffloc := 0;
    end;

   (* set up transmission packet *)
   tdata := 0;
   block[0] := 1; {soh}
   block[1] := packet; {transmitted packet number}
   block[2] := 255 - packet;
   j := 3;
   while j < 131 do
    begin
     block[j] := buffer[buffloc].bytes[0];
     tdata := tdata + block[j];
     block[j + 1] := buffer[buffloc].bytes[1];
     tdata := tdata + block[j + 1];
     j := j + 2;
     buffloc := succ(buffloc);
     if buffloc > 255 then
      if not(eof(tfile)) then
       buffloc := 0
      else
       buffloc := 255;
    end; 
   block[j] := tdata mod 256; {checksum}

   (* send transmission packet *)
   1:
   for i := 0 to 131 do
    sendbyte(block[i]);

   time(stime.intval[0], stime.intval[1]); {start time}
   repeat
    getbyte(comcode.value, flag); {wait for remote ack or nak code}
    time(itime.intval[0], itime.intval[1]); {interval time}
    if (itime.value - stime.value) > 35 then {20 second timeout}
     begin
      timeout := true;
      exit(sendfile);
     end;
   until comcode.bytes[1] in[6, 21];

   if comcode.bytes[1] = 21 then {resend block if nak received}
    goto 1;

   gotoxy(15, 23);
   write(packet);
   packet := succ(packet);
  end;

 sendbyte(4); {send eot code}
 time(stime.intval[0], stime.intval[1]); {start time}
 repeat
  getbyte(comcode.value, flag); {wait for ack code}
  time(itime.intval[0], itime.intval[1]); {interval time}
  if (itime.value - stime.value) > 35 then {20 second timeout}
   begin
    timeout := true;
    exit(sendfile);
   end;
 until comcode.bytes[1] = 6;
end; (* sendfile *)

procedure recvfile;
label
 1;

var
 i, flag, packet, tdata, buffloc, blocksin, errcount : integer;
 comcode : byteword;
 recerr, eotcode : boolean;
 block : array[0..127] of integer;
 buffer : array[0..255] of byteword;

begin (* recvfile *)
(* receive packet *)
 recerr := false;
 eotcode := false;
 buffloc := 0;
 errcount := 0;
 1:
 sendbyte(21); {send nak code}  
 getbyte(comcode.value, flag); {receive soh code}
 if comcode.bytes[1] <> 1 then
  recerr := true; 
 repeat
  if buffloc = 0 then
   fillchar(buffer, 512, chr(26)); {fill buffer with padding character}
  tdata := 0;
  getbyte(comcode.value, flag); {receive packet number}
  packet := comcode.bytes[1];
  getbyte(comcode.value, flag); {receive inverse packet number}
  if (255 - packet) <> comcode.bytes[1] then
   recerr := true;
  for i := 0 to 127 do
   begin
    getbyte(comcode.value, flag); {receive data segment}
    block[i] := comcode.bytes[1];
    tdata := tdata + block[i];
   end;
  getbyte(comcode.value, flag); {receive checksum}
  if (tdata mod 256) <> comcode.bytes[1] then
   recerr := true;
  if recerr then
   begin
    errcount := succ(errcount);
    if errcount > 10 then
     begin
      timeout := true;
      exit(recvfile);
     end
    else
     begin
      recerr := false;
      goto 1;
     end;
   end;
   
(* process packet *)
  i := 0;
  while i < 128 do
   begin
    buffer[buffloc].bytes[0] := block[i];
    buffer[buffloc].bytes[1] := block[i + 1];
    i := i + 2;
    buffloc := succ(buffloc);
   end;
  if buffloc > 255 then
   begin
    buffloc := 0;
    blocksin := blockwrite(tfile, buffer, 1);
   end;
  gotoxy(18, 23);
  write(packet);
  reccount := 0;
  sendbyte(6); {send ack code}
  getbyte(comcode.value, flag); {receive soh or eot code}
  if comcode.bytes[1] = 4 then
   eotcode := true
  else
   if comcode.bytes[1] <> 1 then
    begin
     timeout := true;
     exit(recvfile);
    end;
 until eotcode;
 sendbyte(6); {send ack code}
end; (* recvfile *)

begin (* xmodem *)
 1:
 page(output);
 writeln('(R)eceive file');
 writeln('(S)end file');
 writeln('(Q)uit program', chr(7));
 repeat
  key := getkey;
 until key in[81, 82, 83];
 
 case key of
  81 : exit(program);
  82 : begin
        writeln('receive mode');
        send := false;
       end;
  83 : begin
        writeln('send mode');
        send := true;
       end;
 end;

 repeat
  gotoxy(0, 5);
  writeln('enter filename:', chr(7));
  readln(fname);
  if send then
   reset(tfile, fname)
  else
   rewrite(tfile, fname);
 until ioresult = 0;

 writeln(chr(10), chr(7), '1: rs232/1  2: rs232/2');
 repeat
  key := getkey;
 until key in[49, 50];
 if key = 49 then
  begin
   setrs232(1);
   writeln('using rs232/1');
  end
 else
  begin
   setrs232(2);
   writeln('using rs232/2');
  end;

 writeln(chr(10), chr(7), 'press any key to start transfer...');
 repeat
 until getkey <> 255;

 timeout := false;
 if send then
  begin
   gotoxy(0, 23);
   write('packets sent: ');
   sendfile;
  end
 else
  begin
   gotoxy(0, 23);
   write('packets received: ');
   recvfile;
  end;

 if timeout then
  begin
   gotoxy(0, 21);
   writeln(chr(7), 'transfer error!');
   for d := 1 to 2000 do
    begin
    end;
   timeout := false;
   close(tfile);
   goto 1;
  end;
  
 gotoxy(0, 13);
 writeln('transfer complete!', chr(7));
 writeln('press any key');
 if send then
  close(tfile)
 else
  close(tfile, lock);
 repeat
 until getkey <> 255;
 goto 1;

end. (* xmodem *) 

 

 

RSUTIL.dsk

  • Like 4
  • Thanks 1
Link to comment
Share on other sites

Very neat!  I have been distracted lately by another hobby but I really want to make one of these too.

So you are sending at 1200 bps with TerraTerm character delay set to zero?

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