Hello and custom screen mode (256x160, 2bpp, 8Mhz pixel rate, 10kb)

handy tools that can assist in the development of new software
Post Reply
Arx
Posts: 33
Joined: Sat Aug 20, 2022 10:12 pm
Contact:

Hello and custom screen mode (256x160, 2bpp, 8Mhz pixel rate, 10kb)

Post by Arx »

My first post here on Stardot, so firstly hello.

Secondly, I've been doing some fiddling on BeebEm to try to make a new game and I've got some code here which creates a custom screen mode. It's based on MODE 1 but with a resolution of 256x160, taking only 10kb. I've managed to get the hardware text cursor scrolling playing nicely with it, too. I've seen various older posts talking about custom screen modes but I figured I'd include my code here in case it comes in handy for anyone else. I know that custom screen modes are essential for many of the larger games.

Firstly, run the following program which will create a new row multiplication table. The MOS uses these to speed up screen address calculations. The default in ROM is the 640 times table but with the resolution of this custom screen mode we need a 512 times table instead, otherwise the text cursor will be positioned incorrectly for every new line. Actually... I say 512... it really needs to be the 1024 times table. Why? Because the MOS factors its lookup index by the screen memory map type (stored at &0356), which is 2 in this case, denoting a size of 10kb. Fortunately, the MOS's flexibility is such that it stores in RAM a pointer to the row multiplication table to be used. The location of this pointer is &00E0-E1 (lo/hi bytes). So you just change that to point at the custom table. The table itself is stored at a convenient, seemingly-unused 64 bytes from &050-08F but you can put it anywhere you like if you're confident it won't be overwritten.

Code: Select all

   10REM AM51220
   20REM Arx
   30REM To use as part of the Evolution build process
   40REM Generate a *512*2 row multiplication table for the custom screen mode
   50REM Then save as a data file for loading into &50-&8F spare region
   60REM Memory Map Type 2 is for 10kb, however the MOS factors its row multiplication index by this value, so in fact we need a *1024 table to compensate
   70:
   80PRINT'"Generating x512x2 row lookup table..."
   90r%=&50
  100FOR I%=0 TO 31
  110V%=I%*512*2
  120r%?(I%*2)=V% DIV 256:REM HI byte
  130r%?((I%*2)+1)=V% MOD 256:REM LO byte
  140NEXT I%
  150PRINT'"Done."
  160PRINT'"Saving data file..." 
  170S$="SAVE DMUL512 0050 0090 0050"
  180PRINT S$
  190OSCLI S$
  200PRINT'"Done."
  210END
Next up, the main event... a file which assembles and saves an executable which sets up the custom screen mode. It configures the CRTC registers, the relevant MOS variables and the System VIA. The VideoULA is configured by virtue of beginning in MODE 1, as the same control and palette register values apply. The program probably isn't quite as elegant is it could be but there is emphasis on readability.

Code: Select all

   10REM AMODE20
   20REM Arx
   30REM To use as part of the Evolution build process
   40REM Assemble and save the routine that sets up the game's custom screen mode
   50REM The routine will need only executing once as part of the game loading chain
   60REM After which the space it occupies may be repurposed
   70:
   80REM The MODE 1 default 6845 register settings are held in &C46E - &C4A9
   90REM If MODE 5 is needed instead, these are held in &C486 - &C491
  100:
  110exec%=&2F00
  120PRINT';"Assembling XMODE routine..."
  130:
  140osbyte=&FFF4
  150oswrch=&FFEE
  160crtcr=&FE00:REM Write to this address the number of the CRTC register you want to update
  170crtcv=&FE01:REM Then write to this address the new value of the register number specified just prior
  180sviar=&FE40:REM Write to this address the value of the System VIA's Register B to update
  190:
  200DIM code% 255
  210FOR opt%=4 TO 6 STEP 2
  220P%=exec%
  230O%=code%
  240[
  250OPT opt%
  260:
  270\Start with MODE 1
  280LDA #&16
  290JSR oswrch
  300LDA #1
  310JSR oswrch
  320:
  330\Set 6845 CRTC registers
  340PHP
  350SEI         \Disable maskable interrupts while fiddling with the CRTC
  360LDX #0      \From register R0
  370.loop
  380LDA crt,X   \Load value
  390STX crtcr   \Tell the CRTC which register to write to 
  400STA crtcv   \Tell the CRTC the value to be written to that register
  410INX         \Next register to update  
  420CPX #14     \If there is one
  430BNE loop
  440PLP         \Restore interrupt flag
  450:
  460\Set MOS parameters as closely as the MOS itself does for an official mode
  470LDA #3:STA &360    \Logical colours less 1
  480LDA #16:STA &34F   \Bytes per character 
  490LDA #3:STA &361    \Pixels per byte less 1
  500LDA #17:STA &363   \Colour mask left
  510LDA #136:STA &362  \Colour mask right   
  520LDA #2:STA &356    \Memory map type (2 -> 10kb)
  530LDA #&28:STA &354  \Screen memory length (&5800 to &7FFF -> &28) (10kb)
  540LDA #&58:STA &34E  \High byte of screen memory start address (&5800)
  550LDA #0:STA &352    \Bytes per row (LO)
  560LDA #2:STA &353    \Bytes per row (HI) (00000010 00000000 -> 512) 
  570LDA #0:STA &34C    \Text window width in bytes (LO)
  580LDA #127:STA &34D  \Text window width in bytes (HI)
  590LDA #31:STA &30A   \Text window right
  600LDA #19:STA &309   \Text window bottom
  610LDA #0:STA &304    \Y pixels (LO) 
  620LDA #1:STA &305    \Y pixels (HI) (00000001 00000000 -> 256)
  630LDA #160:STA &306  \X pixels (LO)
  640LDA #0:STA &307    \X pixels (HI) (00000000 10100000 -> 160) 
  650LDA #&0:STA &350   \Window area start address (LO)
  660LDA #&58:STA &351  \Window area start address (HI) 
  670LDA #&58:STA &007  \HIMEM = &5800
  680:
  690\Configure System VIA for 10kb hardware scrolling
  700LDA #12:STA sviar   \Set System VIA Register B value C0=1
  710LDA #13:STA sviar  \Set System VIA Register B value C1=1
  720:
  730\Point MOS to the *512 row multiplication table
  740\Must ensure this data file is loaded in to that location
  750LDA #&50:STA &E0   \Row multiplication table pointer (LO) 
  760LDA #&0:STA &E1    \Row multiplication table pointer (HI)
  770:
  780RTS
  790:
  800.crt
  810EQUB 127  \R0 Horiz. Total Reg.
  820EQUB 64   \R1 Horiz. Displayed Reg. (80)
  830EQUB 90   \R2 Horiz. Sync. Pos. Reg. (98)
  840EQUB 40   \R3 Sync. Width Reg.
  850EQUB 38   \R4 Vert. Tot. Reg.
  860EQUB 0    \R5 Vert. Tot. Adj. Reg.
  870EQUB 20   \R6 Vert. Disp. Reg. (32)
  880EQUB 29   \R7 Vert. Sync. Pos. Reg. (34)
  890EQUB 1    \R8 Interlace+Delay Reg. 
  900EQUB 7    \R9 Scan Lines per Char. Row
  910EQUB 103  \R10 Cursor Start Reg.
  920EQUB 8    \R11 Cursor End Reg.
  930EQUB &B   \R12 Start Address (HI)
  940EQUB 0    \R13 Start Address (LO)
  950:
  960]
  970NEXT
  980:
  990PRINT"Done."''"Assembled Base Address = ";~code%'"Assembled End Address = ";~(O%-1)'"Execution Base Address = ";~exec%'"Execution End Address = ";~(P%-1)
 1000PRINT';"Saving executable..."
 1010S$="SAVE XMODE "+STR$~code%+" "+STR$~O%+" "+STR$~exec%+" "+STR$~exec%
 1020PRINT S$
 1030OSCLI S$
 1040PRINT"Done."
 1050:
 1060END
Having made both files, the last thing to do is write a small test script which loads the first and runs the second.

Code: Select all

   10MODE7
   20*LOAD DMUL512
   30*RUN XMODE
   40PRINTTAB(0,0);"HELLO WORLD"
   50END
My thinking is that *LOAD DMUL512 and *RUN XMODE will be among the first of several files loaded or run during game start-up and, since XMODE will only need running once, its RAM can be repurposed for game logic straight after.

The MOS graphics routines don't work properly yet in this custom screen mode... I suspect at least in part due to lingering screen address calculation issues. I'll see if I can do anything about that, though I imagine most machine code games use their own routines.

P.S. Forgot to mention, I discovered courtesy of this forum that the Advanced User Guide gives incorrect values for C0 and C1 in the System VIA Register B for the different screen size types. I spent a good couple of hours trying to figure out where I was going wrong, until I went trawling online and found the answer here - thanks.
gfoot
Posts: 987
Joined: Tue Apr 14, 2020 9:05 pm
Contact:

Re: Hello and custom screen mode (256x160, 2bpp, 8Mhz pixel rate, 10kb)

Post by gfoot »

Nice!

Isn't it easier to just fill in the lookup table from assembler during initialisation, instead of precalculating it and loading it? The low bytes are all zero and the high bytes will be all the multiple of 4, I think. So you can just use a loop like this (untested but hopefully gives the gist of what i mean):

Code: Select all

    LDA #76 \ 19*4
    LDX #38 \ 19*2
    LDY #0
    SEC
.loop
    STA &51,X
    STY &50,X
    DEX
    DEX
    SBC #4
    BCS loop
Edit
Arx
Posts: 33
Joined: Sat Aug 20, 2022 10:12 pm
Contact:

Re: Hello and custom screen mode (256x160, 2bpp, 8Mhz pixel rate, 10kb)

Post by Arx »

Cheers. Yeah, that's definitely viable. I guess I was preoccupied with splitting stuff out across different files but then XMODE expects to see the lookup table at a certain location which it explicitly defines, so it probably makes sense to get XMODE to generate that data, too, and have one less file flying around.
Arx
Posts: 33
Joined: Sat Aug 20, 2022 10:12 pm
Contact:

Re: Hello and custom screen mode (256x160, 2bpp, 8Mhz pixel rate, 10kb)

Post by Arx »

Improved version which does not require the separate data file. Just run and then just do *XMODE to go into the custom screen mode.

Code: Select all

   10REM AMODE
   20REM Arx
   30REM To use as part of the Evolution build process
   40REM Assemble and save the routine that sets up the game's custom screen mode
   50REM The routine will need only executing once as part of the game loading chain
   60REM After which the space it occupies may be repurposed
   70:
   80REM The MODE 1 default 6845 register settings are held in &C46E - &C4A9
   90REM If MODE 5 is needed instead, these are held in &C486 - &C491
  100:
  110exec%=&2F00 
  120PRINT';"Assembling XMODE routine..."
  130:
  140osbyte=&FFF4
  150oswrch=&FFEE
  160crtcr=&FE00:REM Write to this address the number of the CRTC register you want to update
  170crtcv=&FE01:REM Then write to this address the new value of the register number specified just prior
  180sviar=&FE40:REM Write to this address the value of the System VIA's Register B to update
  190mult=&0050:REM Address of the base of the custom row multiplication table
  200mhi=mult DIV 256
  210mlo=mult MOD 256
  220mult_ptr_hi=&E1:REM Address of the address of the base of the table
  230mult_ptr_lo=&E0
  240:
  250DIM code% 255
  260FOR opt%=4 TO 6 STEP 2
  270P%=exec%
  280O%=code%
  290[
  300OPT opt%
  310:
  320\Start with MODE 1
  330LDA #&16
  340JSR oswrch
  350LDA #1
  360JSR oswrch
  370:
  380\Set 6845 CRTC registers
  390PHP
  400SEI         \Disable maskable interrupts while fiddling with the CRTC
  410LDX #0      \From register R0
  420.crtcloop
  430LDA crt,X   \Load value
  440STX crtcr   \Tell the CRTC which register to write to 
  450STA crtcv   \Tell the CRTC the value to be written to that register
  460INX         \Next register to update  
  470CPX #14     \If there is one
  480BNE crtcloop
  490PLP         \Restore interrupts
  500:
  510\Set MOS parameters as closely as the MOS itself does for an official mode
  520PHP
  530SEI
  540LDA #3:STA &360    \Logical colours less 1
  550LDA #16:STA &34F   \Bytes per character 
  560LDA #3:STA &361    \Pixels per byte less 1
  570LDA #17:STA &363   \Colour mask left
  580LDA #136:STA &362  \Colour mask right   
  590LDA #2:STA &356    \Memory map type (2 -> 10kb)
  600LDA #&28:STA &354  \Screen memory length (&5800 to &7FFF -> &28) (10kb)
  610LDA #&58:STA &34E  \High byte of screen memory start address (&5800)
  620LDA #0:STA &352    \Bytes per row (LO)
  630LDA #2:STA &353    \Bytes per row (HI) (00000010 00000000 -> 512) 
  640LDA #0:STA &34C    \Text window width in bytes (LO)
  650LDA #127:STA &34D  \Text window width in bytes (HI)
  660LDA #31:STA &30A   \Text window right
  670LDA #19:STA &309   \Text window bottom
  680LDA #0:STA &304    \Y pixels (LO) 
  690LDA #1:STA &305    \Y pixels (HI) (00000001 00000000 -> 256)
  700LDA #160:STA &306  \X pixels (LO)
  710LDA #0:STA &307    \X pixels (HI) (00000000 10100000 -> 160) 
  720LDA #&0:STA &350   \Window area start address (LO)
  730LDA #&58:STA &351  \Window area start address (HI) 
  740LDA #&58:STA &007  \HIMEM = &5800
  750PLP
  760:
  770\Configure System VIA for 10kb hardware scrolling
  780\Note - the Advanced User Guide quotes incorrect values
  790PHP
  800SEI
  810LDA #12:STA sviar   \Set System VIA Register B value C0=1
  820LDA #13:STA sviar   \Set System VIA Register B value C1=1
  830PLP
  840:
  850\Generate row multiplication table
  860LDX #63     \Top of 64 byte block     
  870LDA #124    \31 * 512*2 >> 8 
  880CLD         \Clear Decimal flag - we don't want binary coded decimal
  890SEC         \Set Carry flag - needed for looping
  900.mloop
  910LDY #0      \Lo byte is always zero
  920STY mult,X
  930DEX
  940STA mult,X
  950DEX
  960SBC #4
  970BCS mloop
  980:
  990\Point MOS to the *512 row multiplication table
 1000\Must ensure this data file is loaded in to that location
 1010LDA #mlo:STA mult_ptr_lo  \Row multiplication table pointer (LO)
 1020LDA #mhi:STA mult_ptr_hi  \Row multiplication table pointer (HI)
 1030:
 1040\CLS
 1050LDA #&A0
 1060JSR oswrch
 1070LDA #&0C
 1080JSR oswrch
 1090:
 1100\Return
 1110RTS
 1120:
 1130.crt
 1140EQUB 127  \R0 Horiz. Total Reg.
 1150EQUB 64   \R1 Horiz. Displayed Reg. (80)
 1160EQUB 90   \R2 Horiz. Sync. Pos. Reg. (98)
 1170EQUB 40   \R3 Sync. Width Reg.
 1180EQUB 38   \R4 Vert. Tot. Reg.
 1190EQUB 0    \R5 Vert. Tot. Adj. Reg.
 1200EQUB 20   \R6 Vert. Disp. Reg. (32)
 1210EQUB 29   \R7 Vert. Sync. Pos. Reg. (34)
 1220EQUB 1    \R8 Interlace+Delay Reg. 
 1230EQUB 7    \R9 Scan Lines per Char. Row
 1240EQUB 103  \R10 Cursor Start Reg.
 1250EQUB 8    \R11 Cursor End Reg.
 1260EQUB &B   \R12 Start Address (HI)
 1270EQUB 0    \R13 Start Address (LO)
 1280:
 1290]
 1300NEXT
 1310:
 1320PRINT"Done."''"Assembled Base Address = ";~code%'"Assembled End Address = ";~(O%-1)'"Execution Base Address = ";~exec%'"Execution End Address = ";~(P%-1)
 1330PRINT';"Saving executable..."
 1340S$="SAVE XMODE "+STR$~code%+" "+STR$~O%+" "+STR$~exec%+" "+STR$~exec%
 1350PRINT S$
 1360OSCLI S$
 1370PRINT"Done."
 1380:
 1390END
Mario
Posts: 4
Joined: Mon Jun 26, 2006 6:59 pm
Contact:

Re: Hello and custom screen mode (256x160, 2bpp, 8Mhz pixel rate, 10kb)

Post by Mario »

I'm just looking at custom modes as well and trying to get my head around how to configure.

What would I need to do to this code to make it 256x256? I've been looking at the advanced user guide and your code here and I've still got a lot to figure out!

Any pointers would be useful.
RobC
Posts: 3816
Joined: Sat Sep 01, 2007 10:41 pm
Contact:

Re: Hello and custom screen mode (256x160, 2bpp, 8Mhz pixel rate, 10kb)

Post by RobC »

Mario wrote: Thu Oct 20, 2022 6:56 pm What would I need to do to this code to make it 256x256?
I haven't tested any of this but here's a list of alterations that I think should do what you want.

Firstly, we need to change the vertial registers back to their original values (to get 256 rows);

Code: Select all

1200EQUB 32   \R6 Vert. Disp. Reg. (32)
1210EQUB 34   \R7 Vert. Sync. Pos. Reg. (34)
Secondly, we need modify the screen start address as it's now a 16KB mode rather than a 10KB mode. The start address is now &4000 rather than &5800 so we set R12 to be &40/8 = 8:

Code: Select all

1260EQUB &8   \R12 Start Address (HI)
Thirdly, we modify the MOS parameters:

Code: Select all

590LDA #1:STA &356    \Memory map type (1 -> 16kb)
600LDA #&40:STA &354  \Screen memory length (&4000 to &7FFF -> &40) (16kb)
610LDA #&40:STA &34E  \High byte of screen memory start address (&4000)
670LDA #31:STA &309   \Text window bottom
700LDA #0:STA &306  \Y pixels (LO)
710LDA #1:STA &307    \Y pixels (HI) (00000001 00000000 -> 256)
730LDA #&40:STA &351  \Window area start address (HI) 
740LDA #&40:STA &007  \HIMEM = &4000 
Finally, we need to set the screen wraparound for a 16KB mode rather than a 10KB mode:

Code: Select all

  
810LDA #4:STA sviar   \Set System VIA Register B value C0=0
820LDA #5:STA sviar   \Set System VIA Register B value C1=0
Post Reply

Return to “development tools”