You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Further improvements on the SGB border guide (#71)
PR #70 ended with different suggested changes stomping on one another.
Finish the last few tasks mentioned in that PR's discussion.
- fix a comma splice
- explain why wait 4 frames
- emphasize why the screen must be on
- use line comments (;) instead of C block comments (/*...*/)
- transform detection code into a subroutine to eliminate `.init`
- rewrite detection code to use a busy wait loop instead of vblank interrupt
- let the lines settle after each read in case A+Right held on DMG
- store the detection result to a variable
- Remove mention of use of palettes 1-3 and 7
---------
Co-authored-by: Antonio Vivace <[email protected]>
Copy file name to clipboardExpand all lines: website/guides/sgb_border.md
+71-57Lines changed: 71 additions & 57 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -4,13 +4,13 @@ This document aims to help developers of DMG-compatible homebrew with adding Sup
4
4
5
5
We will see how to:
6
6
7
-
- Detect whether we are running on a SGB
7
+
- Detect whether the program is running on a SGB
8
8
- Transfer the border's tiles
9
9
- Transfer and display the border's tilemap and palettes
10
10
11
11
<small>
12
12
13
-
Written by [sylvie (zlago)](https://zlago.github.io/me/), idea (and minor help) by [valentina (coffee bat)](https://coffeebat.neocities.org/), reviews and improvements by [ISSOtm](https://eldred.fr) and [avivace](https://github.com/avivace).
13
+
Written by [sylvie (zlago)](https://zlago.github.io/me/), idea (and minor help) by [valentina (coffee bat)](https://coffeebat.neocities.org/), reviews and improvements by [ISSOtm](https://eldred.fr), [avivace](https://github.com/avivace), and [PinoBatch](https://github.com/pinobatch).
14
14
15
15
</small>
16
16
@@ -41,9 +41,9 @@ An SGB packet consists of:
41
41
42
42
You must set `P1` to `%xx11xxxx` between each pulse.
43
43
44
-
This adds up to 16 Bytes of data (LSB first), If a packet doesn't read all 16 bytes, the unused bytes are ignored.
44
+
This adds up to 16 bytes of data (LSB first). If a packet doesn't read all 16 bytes, the unused bytes are ignored.
45
45
46
-
**You should wait 4 frames between each packet.**
46
+
**You should wait 4 frames between each packet and the next.** This gives the SGB BIOS a chance to receive a packet even if it is doing something else time-consuming.
47
47
48
48
For an example of such routine, see this [code](https://github.com/zlago/violence-gbc/blob/11cfdb6ee8a35e042fa9712484d814e0961cea7c/src/sub.sm83#L413-L463) and the related Pan Docs entry: [SGB Command Packet on Pandocs](https://gbdev.io/pandocs/SGB_Command_Packet.html).
49
49
@@ -53,7 +53,7 @@ This guide glosses over a minor detail, as certain packets can be (albeit unccom
53
53
54
54
Bulk transfer (TRN) packets tell the SGB to copy the contents of the screen to buffers in Super NES work RAM. The `CHR_TRN` and `PCT_TRN` packets are used to send data for SGB borders.
55
55
56
-
For that to function properly, you must:
56
+
For a transfer to function properly, you must prepare VRAM and the LCD registers:
57
57
58
58
1. set `BGP` to `$e4` and `LCDC` to `$91` (screen enabled, BG uses tiles `$8000`-`$8fff` and tilemap `$9800`, WIN and OBJ disabled, BG enabled)
59
59
2. set `SCX` and `SCY` to `$00`
@@ -62,77 +62,91 @@ For that to function properly, you must:
62
62
63
63
You can do 1, 2 and 3 via [this snippet](https://github.com/zlago/snek-gbc/blob/baef0369f57d2b0d58316cb1c28c6cc22475a6c9/code/init.sm83#L208-L230)
64
64
65
-
-**You must load the data into VRAM and enable the screen before sending the TRN packet**
66
-
-**You must wait ~8 frames after each TRN** instead of just 4
65
+
-**You must load the data into VRAM and enable the screen before sending the TRN packet.** The SGB reads TRN payloads from the screen. If rendering is off, there is nothing on the screen to read.
66
+
-**You must wait ~8 frames after each TRN** instead of just 4. The SGB BIOS has to finish what it's doing, receive the packet, and then read the screen.
67
67
68
68
## Detecting SGB
69
69
70
70
Here's how a SGB detection routine should look like:
71
71
72
-
- wait 12 or more frames after the console boots
73
-
- send a `MLT_REQ`, selecting 2 or 4 players (`$89, $01` or `$89, $03`), and wait at least 1 frame
74
-
- attempt to advance the read player (set `P1.5` to 0 then 1)
75
-
- set `P1.4`-`P1.5` to `%11` (`%xx11xxxx`)
76
-
- read `P1.0`-`P1.3`, and check if it either
77
-
* has changed
78
-
* isn't `%1111`
79
-
- advance and reread a few times and branch somewhere if the test keeps failing
72
+
1. Wait 12 or more frames for the SGB BIOS to start listening for packets.
73
+
2. Send a `MLT_REQ` packet selecting 2 or 4 players (`$89, $01` or `$89, $03`), and wait a couple frames for the SGB to receive the packet.
74
+
3. Read the controller. Set `P1` bit 5 to 0, then set `P1` bits 5 and 4 to `%11` (`%xx11xxxx`) to release the key matrix.
75
+
4. Read the low nibble (bits 3-0) of `P1`, and look for a value other than `%1111` (which indicates player 1).
76
+
5. If player 1 was still found, repeat steps 3 and 4 once more, in case the next read indicates player 2.
77
+
6. Optionally turn off multiplayer mode.
80
78
81
-
Let's go over an example snippet ([source](https://github.com/zlago/snek-gbc/blob/baef0369f57d2b0d58316cb1c28c6cc22475a6c9/code/init.sm83#L167-L196)):
79
+
If a non-`%1111` value was found in step 4 either time, the program is running on SGB.
80
+
81
+
A routine like this may be used to detect SGB (modified from [source](https://github.com/zlago/snek-gbc/blob/baef0369f57d2b0d58316cb1c28c6cc22475a6c9/code/init.sm83#L167-L196)):
82
82
83
83
```sm83asm
84
-
; test for SGB
85
-
xor a /* first it enables STAT interrupt, since snek-gbc happens to just have a `reti` as the handler */
86
-
ldh [rLYC], a
87
-
ld a, STATF_LYC
88
-
ldh [rSTAT], a
89
-
ld a, IEF_STAT
90
-
ldh [rIE], a
91
-
ei
92
-
; wait for SGB
93
-
ld b, 12 /* You must wait 12 or more frames before trying to send a packet */
94
-
:halt
95
-
dec b
96
-
jr nz, :-
97
-
; enable multi
98
-
ld hl, Packets.mlt /* send a `MLT_REQ` */
99
-
call Packet
100
-
rept 4 /* then wait 4 more frames, since the SNES won't "read" the packet instantly */
101
-
halt
102
-
endr
103
-
; check if SGB responds
104
-
/* and now We actually try to detect the SGB
105
-
setting `P1.5` to low then high advances the "read player"
106
-
setting `P1.4` and `P1.5` high will make the SGB return which player is currently selected in `P1.0` and `P1.1` */
107
-
lb bc, 5, LOW(rP1) /* b is loaded with 5 which is how many times We try to check if the player changes to anything but P1 */
108
-
:ld a, P1F_4 /* try to advance player */
109
-
ldh [c], a
110
-
ld a, P1F_4|P1F_5 /* then set P1 to return the current player */
111
-
ldh [c], a
112
-
ldh a, [c]
113
-
dec b
114
-
jp z, .init ; give up /* if 5 (4 actually) attempts fail, assume this isn't an SGB */
115
-
cp $ff /* `P1.6` and `P1.7` always return %1, We set `P1.4` and `P1.5` to %1, and DMG, or SGB player 1, return %1111 in `P1.0`-`P1.3` */
116
-
jr z, :- ; /* try again if detection fails */
84
+
SGB_Detect:
85
+
; test for SGB
86
+
di
87
+
call SGB_Wait4Frames
88
+
call SGB_Wait4Frames
89
+
call SGB_Wait4Frames
90
+
91
+
; Send MLT_REQ packet to enable multiplayer
92
+
ld hl, Packets.mltOn ; send MLT_REQ for 2 players
93
+
call SGB_SendPacket
94
+
call SGB_Wait4Frames
95
+
96
+
; Detect the SGB by checking if SGB responded to MLT_REQ.
97
+
; Setting P1.5 to low then high advances the selected player.
98
+
; Setting P1.4 and P1.5 high causes the ICD2 to reflect the
99
+
; selected player in low bits of P1.
100
+
ld b, 4 ; Number of attempts
101
+
ld c, LOW(rP1) ; Address to write
102
+
.loop
103
+
ld a, P1F_4 ; Try to advance player
104
+
ldh [c], a
105
+
ld a, P1F_4|P1F_5 ; Set P1 to return the selected player
106
+
ldh [c], a
107
+
; In case this is DMG and not SGB, let the input lines settle
108
+
; for a few cycles before reading P1 again
109
+
call SGB_Wait4Frames.knownRet
110
+
111
+
ldh a, [c] ; Player 1 has A.0 = 1; players 2 and 4 have A.0 = 0
112
+
cpl ; Invert this
113
+
and %00000001 ; Keep only bit 0, which is 0 for player 1 or nonzero for players 2 and 4
114
+
jr nz, .done
115
+
dec b ; Keep trying until we've cycled through all players
116
+
jr nz, .loop
117
+
.done
118
+
; After this loop, A is $01 for SGB or $00 for not SGB. Remember this
119
+
ld [wIsSGB], a
120
+
121
+
; (Optional) Disable multiplayer
122
+
ld hl, Packets.mltOff ; send MLT_REQ for 2 players
123
+
call nz, SGB_SendPacket
124
+
ret
125
+
126
+
SGB_Wait4Frames:
127
+
ld bc, -(456 * 154 / 16 * 4)
128
+
.loop
129
+
inc c ; inner loop takes 16 T-states per iteration
130
+
jr nz, .loop
131
+
inc b
132
+
jr nz, .loop
133
+
.knownRet
134
+
ret
117
135
```
118
136
119
-
If you adopt this, remember to change `.init` to wherever code should jump to if it's _not_ on SGB, and put code meant to run on SGB after the snippet.
120
-
121
137
### SGB detection notes
122
138
123
-
- It would be a good idea to save somewhere in RAM whether the game is running on an SGB capable device or not, such that if you wish to change the border mid-gameplay, you won't have to perform SGB detection again
124
-
139
+
- It would be a good idea to save somewhere in RAM whether the game is running on an SGB capable device or not, such that if you wish to change the border mid-gameplay, you won't have to perform SGB detection again. The sample code stores it in `wIsSGB`.
125
140
-**If you wish to only use 1 controller for the game, you will have to send another `MLT_REQ` to disable multiplayer** (`$89, $00`)
126
-
127
-
- You can change the waitloop to not use interrupts, or to use di+halt instead
141
+
-`SGB_Wait4Frames` above uses busy waiting. Depending on the structure of your initialization code, you can change it to use vblank interrupts or `di`+`halt` instead.
- 3 palettes of 15 (+ 1 transparent) colors, (up to 45 solid colors total)
135
-
- a 256x224px tilemap (there's a bit more to this, see [notes](#notes))
149
+
- a 256×224-pixel tilemap (there's a bit more to this, see [notes](#notes))
136
150
137
151
## Converting borders
138
152
@@ -174,7 +188,7 @@ See also the related Pan Docs entry: [SGB Command Border](https://gbdev.io/pando
174
188
175
189
1. You can set the first row of tiles to your transparent color to force superfamiconv to put the transparent tile as the 1st tile, however you must then exclude 64 bytes of the tilemap (`incbin "border.pct"` -> `incbin "border.pct", 64`)
176
190
177
-
2.You can use palettes 0-3 and 7, if you really know what you're doing (animated borders yay). You will probably have to edit the border in YY-CHR, as there aren't really any other tools for that.
191
+
2.SGB BIOS reserves palettes 4 through 6 for borders. If you really know what you're doing, you may be able to use palette 0 (the gameplay palette) for animated borders. You will probably have to edit the border in a tile editor such as YY-CHR, as there aren't yet any other tools for that.
178
192
179
193
3. When the SNES lags, scanline 225 of the SGB border will be visible! You can set the topmost row of the 29th row of tiles to black to hide this.
0 commit comments