# Need help with PEEKing the text screen

9 posts / 0 new
Offline
Last seen: 1 month 4 days ago
Joined: Nov 21 2008 - 17:41
Posts: 43
Need help with PEEKing the text screen

I'm trying to convert a BASIC program which PEEKs the text screen, and I'm having a really hard time figuring out a similar formula for the Apple II. The original computer stores screen memory sequentially from top left to bottom right. But the Apple skips seven lines after every 40 characters (that's not entirely accurate, but you probably know what I'm describing.)

Beagle Bros has a short routine to solve this by positioning the cursor using HTAB and VTAB, then PEEK to find the memory location:

PEEK (40) + PEEK (41) * 256 + PEEK (36)

40 and 41 are the base memory address of the cursor position, and 36 is the horizontal position. However, adding this routine to the program, plus its overhead, slows it down considerably.  I would imagine that there's a known mathematical formula to do this more directly, but I can't find it with Google and I can't figure it out on my own. Can anyone point me to a solution? Machine language isn't out of the question, but I'd rather keep it in BASIC. (Timing isn't critical, just inconvenient.)

Offline
Last seen: 4 years 5 months ago
Joined: Dec 2 2013 - 16:09
Posts: 44

I believe this should do it for you, although it won't be fast.

A =(V-INT(V/8)*8)*128+40*INT(V/8)+1024 +H

Where :

V is the vertical line (0 to 23 with 0 at the top)

H is the horizontal column (0-39 with 0 at the left)

A is the address to poke or peek as you see fit.

I'll admit that that line of code makes me cringe.  In assembly it's not much more than a bit of bit manipulation, and could likely be done in 1/100th of the time that it takes applesoft.

Offline
Last seen: 5 hours 24 min ago
Joined: Aug 18 2005 - 16:26
Posts: 428
pre-compute addresses in a lookup array

0 GOSUB 9001 CALL -1184 : VTAB 32 PRINT PEEK(A(0) + 22)3 END900  DIM A(23)910  FOR V = 0 TO 23920      A(V) = (V-INT(V/8)*8)*128+40*INT(V/8)+1024930  NEXT940  RETURN

Offline
Last seen: 5 hours 24 min ago
Joined: Aug 18 2005 - 16:26
Posts: 428
point an array of strings to pre-computed addresses

0 GOSUB 9001 CALL -1184 : VTAB 32 PRINT ASC(MID\$(L\$(0), 23, 1))3 END900  V = 0 : A = 0 : DIM L\$(23) : L\$(0)=L\$(0) : POKE 252, PEEK (131) : POKE 253, PEEK (132) : A = PEEK(252) + PEEK(253) * 256910  FOR V = 0 TO 23920      VTAB V + 1930      POKE A + V * 5, 40940      POKE A + V * 5 + 1, PEEK(40)950      POKE A + V * 5 + 2, PEEK(41)960  NEXT970  RETURN

Offline
Last seen: 3 years 8 months ago
Joined: Jun 5 2019 - 17:43
Posts: 8
A Machine Code Approach

It's an interesting problem, and like a lot of things there are multiple solutions.

To derive the "mathematical formula" for mapping arbitrary 40-column text screen locations to memory addresses, think about the screen memory map by arranging the 24 lines of the screen into a 2-dimensional matrix/array like the following:

```    J
I   0  1  2  3  4  5  6  7
___________________________
0|  0  1  2  3  4  5  6  7
1|  8  9 10 11 12 13 14 15
2| 16 17 18 19 20 21 22 23
```

Where `I` is the "group of 8 lines" of which a line is a member: the 0-group starting at 0x400, the 1-group at 0x428, and the 2-group at 0x450.

Then `J` is the line number of the line within its group.

The algorithm to get from line number Y (0-indexed) to memory address is then:

```xY = 0x400 + (0x28 * I) + (0x80 * J)
```

or in Decimal

```Y = 1024 + (40 * I) + (128 * J)
```

This works because each line starts on a 128-byte boundary and the line-groups start 40 bytes apart.

To do this in a program, you can obtain I and J by dividing the line number by 8: use the quotient for I and the remainder for J, as in `23/8 = 2r7`. In other words, the J value is `Y mod 8` and I is `INT(Y/8)`.

The first piece is easy enough, just sub in the formula for I:

```AY = 1024 + (40 * INT(Y/8)) + (128 * J)
```

Applesoft does not have a modulo operator like Integer BASIC did so to get a BASIC solution you have to build it from parts as already demonstrated. But there are ML approaches too.

It's worth noting that when dealing with powers of 2, `Y mod 2n == Y & (2n-1)` meaning that to get `Y mod 8` you can use the bitwise AND `Y & 7`, but Applesoft doesn't have bitwise operators either. Too bad, since it's such a quick operation in machine code.

One thing you can do is put a ML program in memory and attach it to the Applesoft USR function, which is an underrated feature of Applesoft. This is a two-part thing: you need a ML program in memory, and you have to tell Applesoft where to find it.

First, put a ML program at address \$300 (768) that takes an 8-bit integer and ANDs it with 7. Second, add a `JMP \$0300` statement to the addresses 0A through 0C, which is where the USR function goes to find out what it is supposed to do. This BASIC loader does both these things:

```10 FOR A=768 TO 768+10: READ B: POKE A,B : NEXT A
20 FOR A=10 to 12: READ B: POKE A,B: NEXT A
1000 DATA 32,242,235,165,161,41,7,32,147,235,96
1010 DATA 76,0,3
```

When you call the USR(x) function, that `x` value is put into memory and the subroutine referenced from location \$0A is called. You can see this in action with the next program, which uses our USR function to calculate the value of each potential line number modulo 8.

```10 FOR Y=0 to 23
20 PRINT Y" MOD 8 EQUALS "USR(Y)
30 NEXT
```

Back to the algorithm, we can use that USR function to calculate our J index and complete the calculation:

```AY = 1024 + (40 * INT(Y/8)) + (128 * USR(Y))
```

That gets us the starting address for any screen line Y, then you just add the column position X (counting from 0) for the final address.

```AY = 1024 + (40 * INT(Y/8)) + (128 * USR(Y)) + X
```

Let's try it out with another program:

```5 GOSUB 980 : REM SETUP USR FUNCTION
10 TEXT : HOME : VTAB 20
20 X = 0: Y = 0
30 INPUT "LINE NUMBER   (0-INDEXED)? ";Y
40 INPUT "COLUMN NUMBER (0-INDEXED)? ";X
45 I = INT(Y/8) : J = USR(Y)
50 LA = 1024 + (40 * I) + (128 * J)
60 PRINT "SCREEN LINE ";Y;" IN GROUP ";I;", LINE ";J
70 PRINT "MEMORY ADDRESS ";LA;" + COL";X;" = ";LA+X
80 POKE LA+X,42
90 CALL 64780
100 GOTO 10
200 END

980 FOR A=768 TO 768+10: READ B: POKE A,B : NEXT A
990 FOR A=10 to 12: READ B: POKE A,B: NEXT A
999 RETURN
1000 DATA 32,242,235,165,161,41,7,32,147,235,96
1010 DATA 76,0,3
```

For reference, the USR function at \$0300 is

```    ORG \$300    ; Start at \$300 (768)
JSR \$EBF2   ; Subroutine takes USR input and puts 16bit INT at \$A0 and \$A1
LDA \$A1     ; Our 8-bit input value is at \$A1
AND #7      ; AND it with 7
JSR \$EB93   ; Subroutine takes 8-bit int from A and floats it to FAC
RTS         ; Done
```

You can do this much more simply (and maybe faster?) than with the USR function, but I thought it was an interesting exercise. Jumping into ROM subroutines to manipulate the input and output values like this adds unknown overhead to the function because the USR function puts its argument in the Floating-point ACumulator (FAC) so our simple AND operation is bookended by ROM subroutines to turn that FAC value into an integer and back again.

The basic operation we want to accomplish -- `Y & 7` -- can be performed in just a few bytes of Machine Code:

```    ORG \$300    ; Start at \$300 (768)
LDA \$07     ; Load value at \$7 to A
AND #07     ; AND A with 7
STA \$07     ; Store result
RTS         ; Done
```

Which translates to:

```0300:A9 07 25 07 85 07 60
```

This requires the Line Number Y to be POKEd into address 7, and the return value will be found at the same location after a ML call, as in `POKE 7,Y : CALL 768 : PRINT PEEK(7)`.

Here's the same test program from before with the modified ML code:

```5 GOSUB 980
10 TEXT : HOME : VTAB 20
20 X = 0: Y = 0
30 INPUT "LINE NUMBER   (0-INDEXED)? ";Y
40 INPUT "COLUMN NUMBER (0-INDEXED)? ";X
45 POKE 7,Y : CALL 768 : J = PEEK(7) : I = INT(Y/8)
50 LA = 1024 + (40 * I) + (128 * J)
60 PRINT "SCREEN LINE ";Y;" IN GROUP ";I;", LINE ";J
70 PRINT "MEMORY ADDRESS ";LA;" + COL";X;" = ";LA+X
80 POKE LA+X,42
90 CALL 64780
100 GOTO 10
200 END

980 FOR A=768 TO 768+6: READ B: POKE A,B : NEXT A
999 RETURN
1000 DATA 169,7,37,7,133,7,96
```
Offline
Last seen: 3 months 6 days ago
Joined: Dec 30 2015 - 10:48
Posts: 253
Great explanation...
Great explanation... just learned a lot for probably own purposes... thx.
Offline
Last seen: 1 month 4 days ago
Joined: Nov 21 2008 - 17:41
Posts: 43
Wow, talk about a wealth of

Wow, talk about a wealth of information! These are all very interesting approaches, and I'll need to spend a day delving into them. Thank you all for your advice!

Offline
Last seen: 3 years 8 months ago
Joined: Jun 5 2019 - 17:43
Posts: 8
It Gets Worse / Better

The algorithms described here are pretty legible to a layman compared to what actually happens in the IOU chip to produce a screen location memory address. It uses a lot of bit-banging to take an X,Y coordinate and turn it into a 16-bit memory location.

The process is described in Chapter 7 or Chapter 11 of the "Apple IIe Technical Reference" or "Apple //c Technical Reference Volume 1", respectively, and doesn't make sense the first few times you read it (well, your mileage may vary) but I think I got the hang of it.

Instead of spamming this thread with a bunch of stuff, I'll just link to my GitHub where I've been investigating this line of thought while teaching myself 6502 assembly language for no good reason.

The great thing about the "real" algorithm is that in principle you can use it on any screen for any mode. I don't know that there are a lot of applications for that information, but whatever, it's fun.

Offline
Last seen: 1 month 4 days ago
Joined: Nov 21 2008 - 17:41
Posts: 43
Thanks, I'll check this out

Thanks, I'll check this out later in the week! It 's an interesting problem with very clear parameters, but I just couldn't get all the parts into a working formula by myself. I really appreciate everyone's willingness to delve into this little excercise. I'll share my finished program translation if I ever get it working, although it won't be all that exciting: It's definitely more about the journey than the destination!