@nataliapc/mcp-openmsx 1.1.15 → 1.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (216) hide show
  1. package/README.md +84 -37
  2. package/dist/server.js +101 -40
  3. package/dist/utils.js +42 -2
  4. package/dist/vectordb.js +61 -0
  5. package/package.json +8 -3
  6. package/resources/audio/msx-midi.md +872 -0
  7. package/resources/audio/psg_registers.md +281 -0
  8. package/resources/audio/sound_cartridge_scc.md +123 -0
  9. package/resources/audio/sound_cartridge_scci.md +250 -0
  10. package/resources/audio/toc.json +8 -4
  11. package/resources/book--msx2-technical-handbook/toc.json +1 -1
  12. package/resources/msx-dos/MSX-DOS_2_Environment_Variables.md +1368 -0
  13. package/resources/msx-dos/MSX-DOS_File_extensions.md +154 -0
  14. package/resources/msx-dos/toc.json +13 -0
  15. package/resources/msx-unapi/toc.json +2 -2
  16. package/resources/others/keyboard_matrices.md +243 -0
  17. package/resources/others/toc.json +6 -0
  18. package/resources/programming/asm_callbios.md +79 -0
  19. package/resources/programming/asm_docopy.md +115 -0
  20. package/resources/programming/asm_fast_loops.md +200 -0
  21. package/resources/programming/asm_getslot.md +143 -0
  22. package/resources/programming/asm_interrupts.md +202 -0
  23. package/resources/programming/asm_load_screen.md +240 -0
  24. package/resources/programming/asm_mult_div_shifts.md +487 -0
  25. package/resources/programming/asm_raminpage1.md +56 -0
  26. package/resources/programming/asm_vdp_detection.md +78 -0
  27. package/resources/programming/asm_vdp_routines.md +343 -0
  28. package/resources/programming/asm_z80_routines_collection.md +810 -0
  29. package/resources/programming/basic_wiki/ABS().md +36 -0
  30. package/resources/programming/basic_wiki/AND.md +71 -0
  31. package/resources/programming/basic_wiki/ASC().md +38 -0
  32. package/resources/programming/basic_wiki/ATN().md +36 -0
  33. package/resources/programming/basic_wiki/AUTO.md +39 -0
  34. package/resources/programming/basic_wiki/BASE().md +147 -0
  35. package/resources/programming/basic_wiki/BEEP.md +27 -0
  36. package/resources/programming/basic_wiki/BIN$().md +36 -0
  37. package/resources/programming/basic_wiki/BLOAD.md +63 -0
  38. package/resources/programming/basic_wiki/BSAVE.md +61 -0
  39. package/resources/programming/basic_wiki/CALL.md +391 -0
  40. package/resources/programming/basic_wiki/CALL_ADJUST.md +40 -0
  41. package/resources/programming/basic_wiki/CALL_IMPOSE.md +28 -0
  42. package/resources/programming/basic_wiki/CALL_OPTIONS.md +26 -0
  43. package/resources/programming/basic_wiki/CALL_PAUSE.md +119 -0
  44. package/resources/programming/basic_wiki/CALL_PCMPLAY.md +60 -0
  45. package/resources/programming/basic_wiki/CALL_PCMREC.md +70 -0
  46. package/resources/programming/basic_wiki/CDBL().md +36 -0
  47. package/resources/programming/basic_wiki/CHR$().md +51 -0
  48. package/resources/programming/basic_wiki/CINT().md +36 -0
  49. package/resources/programming/basic_wiki/CIRCLE.md +51 -0
  50. package/resources/programming/basic_wiki/CLEAR.md +39 -0
  51. package/resources/programming/basic_wiki/CLOAD.md +27 -0
  52. package/resources/programming/basic_wiki/CLOAD?.md +31 -0
  53. package/resources/programming/basic_wiki/CLOSE.md +44 -0
  54. package/resources/programming/basic_wiki/CLS.md +51 -0
  55. package/resources/programming/basic_wiki/COLOR.md +143 -0
  56. package/resources/programming/basic_wiki/COLOR=.md +93 -0
  57. package/resources/programming/basic_wiki/COLOR_SPRITE$().md +83 -0
  58. package/resources/programming/basic_wiki/COLOR_SPRITE().md +85 -0
  59. package/resources/programming/basic_wiki/CONT.md +23 -0
  60. package/resources/programming/basic_wiki/COPY.md +215 -0
  61. package/resources/programming/basic_wiki/COPY_SCREEN.md +61 -0
  62. package/resources/programming/basic_wiki/COS().md +37 -0
  63. package/resources/programming/basic_wiki/CSAVE.md +35 -0
  64. package/resources/programming/basic_wiki/CSNG().md +36 -0
  65. package/resources/programming/basic_wiki/CSRLIN.md +33 -0
  66. package/resources/programming/basic_wiki/DATA.md +47 -0
  67. package/resources/programming/basic_wiki/DEFDBL.md +40 -0
  68. package/resources/programming/basic_wiki/DEFINT.md +40 -0
  69. package/resources/programming/basic_wiki/DEFSNG.md +40 -0
  70. package/resources/programming/basic_wiki/DEFSTR.md +40 -0
  71. package/resources/programming/basic_wiki/DEF_FN.md +49 -0
  72. package/resources/programming/basic_wiki/DEF_USR.md +33 -0
  73. package/resources/programming/basic_wiki/DELETE.md +49 -0
  74. package/resources/programming/basic_wiki/DIM.md +59 -0
  75. package/resources/programming/basic_wiki/DRAW.md +77 -0
  76. package/resources/programming/basic_wiki/ELSE.md +45 -0
  77. package/resources/programming/basic_wiki/END.md +32 -0
  78. package/resources/programming/basic_wiki/EOF().md +36 -0
  79. package/resources/programming/basic_wiki/EQV.md +76 -0
  80. package/resources/programming/basic_wiki/ERASE.md +35 -0
  81. package/resources/programming/basic_wiki/ERL.md +34 -0
  82. package/resources/programming/basic_wiki/ERR.md +143 -0
  83. package/resources/programming/basic_wiki/ERROR.md +145 -0
  84. package/resources/programming/basic_wiki/EXP().md +38 -0
  85. package/resources/programming/basic_wiki/FIELD.md +48 -0
  86. package/resources/programming/basic_wiki/FIX().md +44 -0
  87. package/resources/programming/basic_wiki/FN.md +61 -0
  88. package/resources/programming/basic_wiki/FOR...NEXT.md +80 -0
  89. package/resources/programming/basic_wiki/FRE().md +66 -0
  90. package/resources/programming/basic_wiki/GET_DATE.md +60 -0
  91. package/resources/programming/basic_wiki/GET_TIME.md +34 -0
  92. package/resources/programming/basic_wiki/GOSUB.md +41 -0
  93. package/resources/programming/basic_wiki/GOTO.md +41 -0
  94. package/resources/programming/basic_wiki/HEX$().md +36 -0
  95. package/resources/programming/basic_wiki/IF...GOTO...ELSE.md +55 -0
  96. package/resources/programming/basic_wiki/IF...THEN...ELSE.md +50 -0
  97. package/resources/programming/basic_wiki/IMP.md +83 -0
  98. package/resources/programming/basic_wiki/INKEY$.md +65 -0
  99. package/resources/programming/basic_wiki/INP().md +33 -0
  100. package/resources/programming/basic_wiki/INPUT$().md +51 -0
  101. package/resources/programming/basic_wiki/INPUT.md +93 -0
  102. package/resources/programming/basic_wiki/INSTR().md +44 -0
  103. package/resources/programming/basic_wiki/INT().md +44 -0
  104. package/resources/programming/basic_wiki/INTERVAL.md +57 -0
  105. package/resources/programming/basic_wiki/KEY().md +51 -0
  106. package/resources/programming/basic_wiki/KEY.md +254 -0
  107. package/resources/programming/basic_wiki/LEFT$().md +39 -0
  108. package/resources/programming/basic_wiki/LEN().md +36 -0
  109. package/resources/programming/basic_wiki/LET.md +68 -0
  110. package/resources/programming/basic_wiki/LINE.md +74 -0
  111. package/resources/programming/basic_wiki/LINE_INPUT.md +79 -0
  112. package/resources/programming/basic_wiki/LIST.md +58 -0
  113. package/resources/programming/basic_wiki/LLIST.md +43 -0
  114. package/resources/programming/basic_wiki/LOAD.md +56 -0
  115. package/resources/programming/basic_wiki/LOCATE.md +67 -0
  116. package/resources/programming/basic_wiki/LOG().md +36 -0
  117. package/resources/programming/basic_wiki/LPOS().md +31 -0
  118. package/resources/programming/basic_wiki/LPRINT.md +46 -0
  119. package/resources/programming/basic_wiki/MAXFILES.md +39 -0
  120. package/resources/programming/basic_wiki/MERGE.md +54 -0
  121. package/resources/programming/basic_wiki/MID$().md +72 -0
  122. package/resources/programming/basic_wiki/MOD.md +39 -0
  123. package/resources/programming/basic_wiki/MOTOR.md +46 -0
  124. package/resources/programming/basic_wiki/NEW.md +27 -0
  125. package/resources/programming/basic_wiki/NOT.md +61 -0
  126. package/resources/programming/basic_wiki/OCT$().md +36 -0
  127. package/resources/programming/basic_wiki/ON...GOSUB.md +45 -0
  128. package/resources/programming/basic_wiki/ON...GOTO.md +42 -0
  129. package/resources/programming/basic_wiki/ON_ERROR_GOTO.md +61 -0
  130. package/resources/programming/basic_wiki/ON_INTERVAL_GOSUB.md +54 -0
  131. package/resources/programming/basic_wiki/ON_KEY_GOSUB.md +56 -0
  132. package/resources/programming/basic_wiki/ON_SPRITE_GOSUB.md +41 -0
  133. package/resources/programming/basic_wiki/ON_STOP_GOSUB.md +56 -0
  134. package/resources/programming/basic_wiki/ON_STRIG_GOSUB.md +70 -0
  135. package/resources/programming/basic_wiki/OPEN.md +103 -0
  136. package/resources/programming/basic_wiki/OR.md +75 -0
  137. package/resources/programming/basic_wiki/OUT.md +35 -0
  138. package/resources/programming/basic_wiki/PAD().md +110 -0
  139. package/resources/programming/basic_wiki/PAINT.md +66 -0
  140. package/resources/programming/basic_wiki/PDL().md +53 -0
  141. package/resources/programming/basic_wiki/PEEK().md +44 -0
  142. package/resources/programming/basic_wiki/PLAY().md +58 -0
  143. package/resources/programming/basic_wiki/PLAY.md +196 -0
  144. package/resources/programming/basic_wiki/POINT.md +52 -0
  145. package/resources/programming/basic_wiki/POKE.md +51 -0
  146. package/resources/programming/basic_wiki/POS().md +36 -0
  147. package/resources/programming/basic_wiki/PRESET.md +61 -0
  148. package/resources/programming/basic_wiki/PRINT.md +179 -0
  149. package/resources/programming/basic_wiki/PSET.md +82 -0
  150. package/resources/programming/basic_wiki/PUT_KANJI.md +93 -0
  151. package/resources/programming/basic_wiki/PUT_SPRITE.md +143 -0
  152. package/resources/programming/basic_wiki/READ.md +45 -0
  153. package/resources/programming/basic_wiki/REM.md +42 -0
  154. package/resources/programming/basic_wiki/RENUM.md +78 -0
  155. package/resources/programming/basic_wiki/RESTORE.md +52 -0
  156. package/resources/programming/basic_wiki/RESUME.md +45 -0
  157. package/resources/programming/basic_wiki/RETURN.md +47 -0
  158. package/resources/programming/basic_wiki/RIGHT$().md +39 -0
  159. package/resources/programming/basic_wiki/RND().md +51 -0
  160. package/resources/programming/basic_wiki/RUN.md +56 -0
  161. package/resources/programming/basic_wiki/SAVE.md +65 -0
  162. package/resources/programming/basic_wiki/SCREEN.md +164 -0
  163. package/resources/programming/basic_wiki/SET_ADJUST.md +66 -0
  164. package/resources/programming/basic_wiki/SET_BEEP.md +76 -0
  165. package/resources/programming/basic_wiki/SET_DATE.md +103 -0
  166. package/resources/programming/basic_wiki/SET_PAGE.md +52 -0
  167. package/resources/programming/basic_wiki/SET_PASSWORD.md +75 -0
  168. package/resources/programming/basic_wiki/SET_PROMPT.md +61 -0
  169. package/resources/programming/basic_wiki/SET_SCREEN.md +100 -0
  170. package/resources/programming/basic_wiki/SET_SCROLL.md +55 -0
  171. package/resources/programming/basic_wiki/SET_TIME.md +83 -0
  172. package/resources/programming/basic_wiki/SET_TITLE.md +87 -0
  173. package/resources/programming/basic_wiki/SET_VIDEO.md +49 -0
  174. package/resources/programming/basic_wiki/SGN().md +38 -0
  175. package/resources/programming/basic_wiki/SIN().md +36 -0
  176. package/resources/programming/basic_wiki/SOUND.md +188 -0
  177. package/resources/programming/basic_wiki/SPACE$().md +38 -0
  178. package/resources/programming/basic_wiki/SPC().md +34 -0
  179. package/resources/programming/basic_wiki/SPRITE$().md +50 -0
  180. package/resources/programming/basic_wiki/SPRITE.md +31 -0
  181. package/resources/programming/basic_wiki/SQR().md +32 -0
  182. package/resources/programming/basic_wiki/STICK().md +70 -0
  183. package/resources/programming/basic_wiki/STOP.md +70 -0
  184. package/resources/programming/basic_wiki/STR$().md +37 -0
  185. package/resources/programming/basic_wiki/STRIG().md +82 -0
  186. package/resources/programming/basic_wiki/STRING$().md +42 -0
  187. package/resources/programming/basic_wiki/SWAP.md +62 -0
  188. package/resources/programming/basic_wiki/TAB().md +38 -0
  189. package/resources/programming/basic_wiki/TAN().md +36 -0
  190. package/resources/programming/basic_wiki/TIME.md +59 -0
  191. package/resources/programming/basic_wiki/TROFF.md +21 -0
  192. package/resources/programming/basic_wiki/TRON.md +39 -0
  193. package/resources/programming/basic_wiki/USR().md +66 -0
  194. package/resources/programming/basic_wiki/VAL().md +36 -0
  195. package/resources/programming/basic_wiki/VARPTR().md +50 -0
  196. package/resources/programming/basic_wiki/VDP().md +103 -0
  197. package/resources/programming/basic_wiki/VPEEK().md +46 -0
  198. package/resources/programming/basic_wiki/VPOKE.md +48 -0
  199. package/resources/programming/basic_wiki/WAIT.md +38 -0
  200. package/resources/programming/basic_wiki/WIDTH.md +76 -0
  201. package/resources/programming/basic_wiki/XOR.md +72 -0
  202. package/resources/programming/basic_wiki/_toc.json +871 -0
  203. package/resources/programming/dos_error_handling.md +85 -0
  204. package/resources/programming/toc.json +51 -36
  205. package/resources/programming/vdp_commands_speed.md +147 -0
  206. package/resources/programming/vdp_programming_faq.md +55 -0
  207. package/resources/programming/vdp_programming_tutorial.md +390 -0
  208. package/resources/programming/vdp_screensplit_programming_guide.md +166 -0
  209. package/resources/programming/vdp_scrolling_on_msx.md +124 -0
  210. package/resources/programming/vdp_the_yjk_screen_modes.md +227 -0
  211. package/resources/programming/vdp_v9938_vram_timings.md +539 -0
  212. package/resources/programming/vdp_v9938_vram_timings_part_2.md +281 -0
  213. package/resources/sdcc/toc.json +1 -1
  214. package/vector-db/index.json +1 -0
  215. /package/resources/msx-unapi/{Ethernet_UNAPI_specification_1.1.md → Ethernet_UNAPI_specification_1_1.md} +0 -0
  216. /package/resources/msx-unapi/{MSX_UNAPI_specification_1.1.md → MSX_UNAPI_specification_1_1.md} +0 -0
@@ -0,0 +1,390 @@
1
+ # VDP programming tutorial
2
+
3
+ This article is meant to be a tutorial for beginning Assembly programmers who want to access the MSX VDP. Knownledge of Z80 assembly is expected. I’ll start out with explaining a little about the three main features of the VDP, then I will go into more detail on how to use them, giving some sample routines inbetween, then I will tell something about the palette, and finally I will give an small example using all addressing methods.
4
+
5
+ ## Table of contents:
6
+
7
+ - [The VDP basics](#the-vdp-basics)
8
+ - [Writing and reading VDP registers](#writing-and-reading-vdp-registers)
9
+ - [Direct register access](#direct-register-access)
10
+ - [Indirect register access](#indirect-register-access)
11
+ - [Status register access](#status-register-access)
12
+ - [Writing and reading the VRAM](#writing-and-reading-the-vram)
13
+ - [VRAM access timing](#vram-access-timing)
14
+ - [Executing VDP commands](#executing-vdp-commands)
15
+ - [Setting the palette](#setting-the-palette)
16
+ - [Programming example](#programming-example)
17
+
18
+ ## The VDP basics
19
+
20
+ There are basically three VDPs for when it concerns MSX computers, the MSX1 its TMS9918A (by Texas Instruments), the MSX2 its v9938, and the MSX2+ and turboR’s v9958 (both by Yamaha). There is also the Graphics9000 (v9990, also by Yamaha), which is pretty cool aswell, but we won’t talk about that one. This document is a guide to programming the v9938, with some remarks about the v9958 inbetween. Although the v9958 definitely has some great additional features like high-color screen modes and horizontal scrolling, the basics are the same.
21
+
22
+ The MSX v9938 VDP has three main features:
23
+
24
+ - It has VRAM. This is a special 128kb RAM area dedicated to the VDP, in which it stores its image data. The MSX CPU can only access it through the VDP.
25
+ - It has a set of registers. With these registers you can control the VDP’s behaviour, amongst others in which screenmode it operates, the refresh frequency, and much much more. Especially when used in combination with screensplits they enable you to achieve amazing things. In total there are 39 write-only registers, and 10 read-only (status) registers. On the v9958 VDP, used in amongst others all MSX 2+ and turboR computers, there are 3 additional write-only registers.
26
+ - It has a command unit which can perform VDP-based operations. These operations can vary from copying a specified area of an image in different speed modes to filling boxes and drawing lines. This is really one of the most useful features of the MSX VDP, because it can perform these operations independently of the Z80 CPU, and is also faster at it. Just give the VDP your 15-byte command and let it do the hard work.
27
+
28
+ The VDP has got 4 I/O ports through which communication with the CPU is done. They are referred to as VDP ports #0 - #3. On MSX2 computers, one officially needs to read the VDP ‘base’ port address (being the address of the first port) from addresses 6 and 7 in the BIOS. However, from the MSX2+ computer on this address has been standardized to #98. The only devices which actually used this feature are MSX2 expansion sets for MSX1 computers, and those are very rare due to amongst others the lack of software support, and because buying a new MSX2 computer would probably have been cheaper. So in practice, you can just assume them to be present in the #98-#9B range, which is much faster to program for. I’ll usually refer to the I/O port number (#98 for example) instead of the VDP’s port number (being #0 in this case).
29
+
30
+ ## Writing and reading VDP registers
31
+
32
+ The VDP registers are pretty much the most important for controlling the VDP’s behaviour. As mentioned before, you can make them set a screen mode and lots of other display properties, and particularly when used on screensplits they can enable you to pull amazing visual effects. They can only be written to, and not read from, so if you’re smart you mirror them in the RAM everytime you write to them, especially when it concerns the mode registers. The MSX BIOS also does this, I will tell you the specific addresses later on. The VDP registers are generally referred to as r#number, number being the register number (duh :)), e.g. r#23, which is the display offset (vertical scroll) register.
33
+
34
+ If you have been a avid MSX-Basic programmer, you will probably notice something odd, because in Basic, VDP register 23 is referred to as VDP(24)! This is indeed true for Basic, and the reason for that is that on the MSX1, with the TMS9918A VDP, VDP(8) was used to read status register 0. However, on the MSX2 VDP suddenly an 8th register appeared. To fix this they decided to increase the register number with 1 (and use negative numbers for the new status registers). So VDP(9) in Basic is actually r#8 and VDP(10) is r#9.
35
+
36
+ The actual VDP register 24 is not present on the v9938 nor on the v9958. It was used on the v9948 – apparantly a VDP dedicated to the Korean market, which added another text mode specifically for use with the Korean character set. The v9958 added new registers to the v9938 registerset aswell, those are registers 25-27, but it hasn’t got a register 24.
37
+
38
+ Ah, I still need to list the promised MSX BIOS VDP register mirror addresses:
39
+
40
+ ```
41
+ #F3DF - #F3E6: registers 0-7
42
+ #FFE7 - #FFF6: registers 8-23
43
+ #FFFA - #FFFC: registers 25-27
44
+ ```
45
+
46
+ I myself often have the start addresses set as 3 labels, VDP_0, VDP_8 (-8) and VDP_25 (-25), and refer to for example register 23 with VDP_8 + 23. Another setup I use even more is my own VDP array of 28 bytes. At the start of my program I copy the values from the BIOS into my own array, and after that I simply load the value of a certain VDP register in A by using LD A,(VDP+9).
47
+
48
+ ### Direct register access
49
+
50
+ Anyways, let’s talk about how to actually write to them registers ;). The VDP registers can be addressed in two ways, direct and indirect. Usually the direct way is used, but the indirect method is also practical in some situations. For direct register access, what you have to do is write the value to port #99 first, and then write the register number with bit 8 set (in other words, +128). Here is the method definition from the v9938 application manual:
51
+
52
+ ```
53
+ MSB 7 6 5 4 3 2 1 0 LSB
54
+ +---+---+---+---+---+---+---+---+
55
+ Port #1 First byte |D7 |D6 |D5 |D4 |D3 |D2 |D1 |D0 | DATA
56
+ +===+===+===+===+===+===+===+===+
57
+ Second byte | 1 | 0 |R5 |R4 |R3 |R2 |R1 |R0 | REGISTER #
58
+ +---+---+---+---+---+---+---+---+
59
+ ```
60
+
61
+ So the actual code with which you change a register’s value will look something like this:
62
+
63
+ ```assembler
64
+ ld a,value
65
+ di
66
+ out (#99),a
67
+ ld a,regnr + 128
68
+ ei
69
+ out (#99),a
70
+ ```
71
+
72
+ Note the DI and the EI instructions inbetween. It is VERY important that you disable the interrupts during the 2 OUTs. This is because the VDP registers are also changed on the interrupt, and if an interrupt were to occur right inbetween these two OUT instructions the results would be unpredictable. The EI is put before the OUT here since the EI has a delay of 1 instruction on the Z80 before it re-enables the interrupts, and if possible one should keep the interrupts disabled as shortly as possible (some interrupts like line interrupts for screensplits or RS232 interrupts need responses as fast as possible).
73
+
74
+ There is no speed limit on reading and writing VDP registers. Well, there is one obviously, but existing MSX CPUs don’t reach it. So feel free to have just a XOR A between two OUT instructions, or even use consecutive OUTI or OUT (C),r instructions.
75
+
76
+ Another thing, if you are going to make a macro for setting the VDP register, I strongly recommend you to not include the DI and EI instructions. If you do that the status of the interrupt is not clearly visible in the code anymore, which could result in puzzling bugs in situations where the interrupt must stay disabled (for example in case you want to select a status register - see below). The downside of this is that you can’t put the EI before the 2nd OUT anymore, only after the macro. But that’s not that bad either, while the bugs this could cause can be mind-sizzling.
77
+
78
+ ### Indirect register access
79
+
80
+ There is also the other method of addressing the registers, which is, as said before, the indirect method. This means that you can specify the register to write to once, and then repeatedly write values, which is about twice as fast. However the register needs to be the same for all values, or it has to be a successive range of registers (indirect register writing supports auto incrementing). Indirect register writing is done by writing the register number to r#17, also specifying whether to auto-increment, and then writing the desired values to port #9B:
81
+
82
+ ```
83
+ MSB 7 6 5 4 3 2 1 0 LSB
84
+ +---+---+---+---+---+---+---+---+
85
+ Register #17 |AII| 0 |R5 |R4 |R3 |R2 |R1 |R0 | REGISTER #
86
+ +-+-+---+---+---+---+---+---+---+
87
+ |-- 1: Auto increment inhibit
88
+ +-- 0: Auto increment on
89
+
90
+ +---+---+---+---+---+---+---+---+
91
+ Port #3 |D7 |D6 |D5 |D4 |D3 |D2 |D1 |D0 | DATA
92
+ +---+---+---+---+---+---+---+---+
93
+ ```
94
+
95
+ Code example:
96
+
97
+ ```assembler
98
+ ld a,regnr ; add +128 for no auto increment
99
+ di
100
+ out (#99),a
101
+ ld a,17 + 128
102
+ ei
103
+ out (#99),a
104
+
105
+ ld b,number_of_bytes
106
+ ld c,#9B ; you can also write ld bc,#nn9B, which is faster
107
+ ld hl,address
108
+ otir
109
+ ```
110
+
111
+ Note that since VDP programming can be very tight, especially on screensplits, you often need the fastest solution possible. In that case, consider unrolling the OTIR to OUTIs as discussed in the Fast Loops article.
112
+
113
+ ### Status register access
114
+
115
+ Aside from the normal registers there are also the status registers. Those can only be read, although in some cases status bits get reset when they’re read. The status registers are usually referred to as s#number, for example s#0 (being the default status register), and they contain information about interrupts, sprite status, and also the VDP ID number with which you can identify what type of VDP it is (the v9938 has ID 0, the v9958 has ID 2, and the mysterious v9948 probably had ID 1).
116
+
117
+ In order to read a status register one needs to write the number of the status register in r#15, and after that the status register’s value can be read from port #99:
118
+
119
+ ```
120
+ MSB 7 6 5 4 3 2 1 0 LSB
121
+ +---+---+---+---+---+---+---+---+
122
+ Register #15 | 0 | 0 | 0 | 0 |S3 |S2 |S1 |S0 | Status register
123
+ +===+===+===+===+===+===+===+===+
124
+ Port #1 Read data |D7 |D6 |D5 |D4 |D3 |D2 |D1 |D0 | DATA
125
+ +---+---+---+---+---+---+---+---+
126
+ ```
127
+
128
+ An important thing to remember is that with the common BIOS interruptroutine enabled, s#0 must always be enabled. So if you select another status register, keep the interrupts disabled until after you’ve read it and selected back s#0. Also note that it is good practice to have the interrupts disabled for a time period as short as possible, in other words you shouldn’t poll for a certain status register while keeping the interrupts disabled. Switch back to s#0 and enable the ints regularly.
129
+
130
+ Some example code to read out a status register:
131
+
132
+ ```assembler
133
+ ld a,statusregnr
134
+ di
135
+ out (#99),a
136
+ ld a,15 + 128
137
+ out (#99),a
138
+ in a,(#99)
139
+ ex af,af'
140
+ xor a ; ld a,0
141
+ out (#99),a
142
+ ld a,15 + 128
143
+ ei
144
+ out (#99),a
145
+ ex af,af'
146
+ ret
147
+ ```
148
+
149
+ ## Writing and reading the VRAM
150
+
151
+ Now, for the VRAM... It is pretty obvious that the CPU needs a means to write data to it, otherwise it would be quite hard to load an image into the VRAM. Aside from that, the VRAM access is also used a lot to update tables within the VRAM, like for example the sprite tables, and in (tile-based) screen modes 1-4 the pattern and attribute tables aswell. Especially in those screen modes you can pull some really cool effects using this.
152
+
153
+ Since the VRAM is not directly connected to the CPU, communication with it needs to be done through the VDP. On the MSX, the process of writing bytes to the VRAM consists of two steps, first one needs to set the VDP’s ‘address counter’ and the mode (read or write access), and then the program can output (or input) a number of bytes to the VDP, which is interfaced with the VRAM. The setting of the address counter has to be done like this:
154
+
155
+ 1. set the address counter bits 14-16 in register 14.
156
+ 2. set the address counter bits 0-7.
157
+ 3. set the address counter bits 8-13 and specify whether to read or to write.
158
+
159
+ The setting of the upper three bits in register 14 was added in the v9938 VDP (as opposed to the MSX1 TMS9918A) because of the larger amount of VRAM, 128kb instead of 16kb, and hence the larger addressing space. Anyways, those bits need to be set first, and then the bits 0-13 have to be written using two consecutive OUTs to port #99. To clarify a bit more:
160
+
161
+ ```
162
+ MSB 7 6 5 4 3 2 1 0 LSB
163
+ +---+---+---+---+---+---+---+---+
164
+ Register #14 | 0 | 0 | 0 | 0 | 0 |A16|A15|A14| VRAM access base
165
+ +---+---+---+---+---+---+---+---+ address register
166
+
167
+ MSB 7 6 5 4 3 2 1 0 LSB
168
+ +---+---+---+---+---+---+---+---+
169
+ Port #1 First byte |A7 |A6 |A5 |A4 |A3 |A2 |A1 |A0 | VRAM access base
170
+ +===+===+===+===+===+===+===+===+ address registers
171
+ Second byte | X | X |A13|A12|A11|A10|A9 |A8 |
172
+ +-+-+-+-+---+---+---+---+---+---+
173
+ 0 0: Read
174
+ 0 1: Write
175
+ ```
176
+
177
+ After having done this, you can read or write the data from or to port #98. After each VRAM read/write access, the address counter is automatically increased, so if you use repeated reads/writes you don’t need to set the address counter again all the time. Note however that you can’t mix reads and writes – if you wish to change from reading to writing mode or vice versa you need to re-set the address with the read/write bit set appropriately.
178
+
179
+ On the TMS9918 the VRAM address pointer gets modified when you write to a register. Therefore, on MSX1 you must keep the interrupts disabled while writing to or reading from VRAM. Fortunately the V9938’s VRAM address pointer is not affected by register writes, so this restriction does not apply to MSX2.
180
+
181
+ ### VRAM access timing
182
+
183
+ It is important to know that there is a speed limit when accessing the VRAM. How fast you can write exactly depends on the screen mode you’re in and whether you have sprites enabled, etc. The TMS9918 is the slowest, and in the worst case requires you to space your reads and writes 29 T-states apart. Notably, this is slower than the OTIR and INIR instructions (23 cycles), so use the following code in stead (exactly 29 cycles):
184
+
185
+ ```assembler
186
+ OutiToVram:
187
+ outi
188
+ jp nz,OutiToVram
189
+ ```
190
+
191
+ The V9938 is quite a bit faster, reads and writes only need to be 15 T-states apart. There is one exception: screen 0 requires a 20 T-states wait, both in width 40 and 80 modes. Note that the TMS9918 is actually faster in this screen mode, so make sure to test screen 0 programs on MSX2 hardware.
192
+
193
+ What this means is that in MSX2 software you can safely use the OTIR and INIR instructions to output bulk data to the VRAM. If you’re not in screen 0, you can also safely use OUTI and INI instructions, refer to the Fast Loops article for more details on how you can access the VRAM as quickly as possible by creating fast 16-bit loops and unrolling the OTIR / INIR instructions.
194
+
195
+ Finally, during vertical blanking or when the screen is disabled, there is no speed limit. This applies to both the TMS9918 and the V9938. When you intend to exploit this fact, please be aware that at 60Hz, the vertical blanking period is shorter than at 50Hz. Test your code on both European and Japanese machines.
196
+
197
+ For the exact details of TMS9918 VRAM access speeds, consult section 2.1.5 of the TMS9918 application manual. For details about the V9938 timing consult Wouter Vermaelen’s V9938 VRAM timings (part II) articles. Finally, note that all V9938 timings also apply to the V9958.
198
+
199
+ Minimum VRAM access timings in 3.58 MHz Z80 cycles
200
+
201
+ |Screen mode|VDP mode|TMS9918|V9938 / V9958|
202
+ |---|---|:-:|:-:|
203
+ |screen 0, width 40|TEXT 1|12|20|
204
+ |screen 0, width 80|TEXT 2||20|
205
+ |screen 1|GRAPHIC 1|29|15|
206
+ |screen 2|GRAPHIC 2|29|15|
207
+ |screen 3|MULTICOLOR|13|15|
208
+ |screen 4|GRAPHIC 3||15|
209
+ |screen 5|GRAPHIC 4||15|
210
+ |screen 6|GRAPHIC 5||15|
211
+ |screen 7|GRAPHIC 6||15|
212
+ |screen 8|GRAPHIC 7||15|
213
+
214
+ Here are example routines to set the VDP for reading/writing the VRAM:
215
+
216
+ ```assembler
217
+ ;
218
+ ; Set VDP address counter to write from address AHL (17-bit)
219
+ ; Enables the interrupts
220
+ ;
221
+ SetVdp_Write:
222
+ rlc h
223
+ rla
224
+ rlc h
225
+ rla
226
+ srl h
227
+ srl h
228
+ di
229
+ out (#99),a
230
+ ld a,14 + 128
231
+ out (#99),a
232
+ ld a,l
233
+ out (#99),a
234
+ ld a,h
235
+ or 64
236
+ ei
237
+ out (#99),a
238
+ ret
239
+ ```
240
+
241
+ ```assembler
242
+ ;
243
+ ; Set VDP address counter to read from address AHL (17-bit)
244
+ ; Enables the interrupts
245
+ ;
246
+ SetVdp_Read:
247
+ rlc h
248
+ rla
249
+ rlc h
250
+ rla
251
+ srl h
252
+ srl h
253
+ di
254
+ out (#99),a
255
+ ld a,14 + 128
256
+ out (#99),a
257
+ ld a,l
258
+ out (#99),a
259
+ ld a,h
260
+ ei
261
+ out (#99),a
262
+ ret
263
+ ```
264
+
265
+ ## Executing VDP commands
266
+
267
+ And finally, we arrived at the VDP commands. For screen modes 0-4 they are pretty useless, but for screen 5 and up they are definitely one of the coolest features of the VDP. They offer you the possibility to let the VDP perform a couple of actions ranging from copying an area of the screen to drawing a line. It also offers several variants of the same action, which offer more or less possibilities in exchange for speed. For example, the HMMM, LMMM and YMMM commands all copy an area of the screen, but HMMM is significantly faster than LMMM, and YMMM even more so, however the restrictions on YMMM make it only useful in a number of occasions. Refer to the article about VDP commands speed measurements for more details on the command speeds. Take a look at the COMMANDS section on page 54 and onwards of the v9938 application manual for detailed descriptions of the several commands.
268
+
269
+ The VDP expects its command parameters to be set in registers 32-45, and the final command code in register 46. The fastest and easiest way to do this is by using the indirect register access method. The parameter of a function which does that (DoCopy, given below) could then be a pointer to a 15-byte VDP command data block (illustrated in the programming example at the bottom). But before a new command is sent to r#32-r#46 the program should first check the CE bit in s#2 (bit 0). This bit indicates whether the previously given command has finished or not. If it hasn’t, you should wait with giving the next command, or the previous one will be aborted. If abortion is what you want, there is a special STOP command for that.
270
+
271
+ As you might already have guessed, the VDP executes the command independantly of the processor (on nowadays’ PC video cards this is called hardware accelleration... gah, we MSX-ers have had that for a long time already! ;)). In effect this means that the routine returns immediately after issueing the VDP command and in the meanwhile the CPU can do something else. Only if you immediately issue another command the CPU has to wait for the VDP to finish.
272
+
273
+ Note about the v9958: if you set bit 6 of r#25, using VDP commands in screen modes 0-4 actually is supported. I it kind of hard to think of a real good use for it, but maybe you’ll find one someday :). Er, lemme think, it could for example be used to speed up vertical scrolling in screens 0 and 1. Or actually any tile-based scrolling I guess.
274
+
275
+ Here is the DoCopy routine, read the small source code article about DoCopy on how to speed it up a little more.
276
+
277
+ ```assembler
278
+ ;
279
+ ; Fast DoCopy, by Grauw
280
+ ; In: HL = pointer to 15-byte VDP command data
281
+ ; Out: HL = updated
282
+ ;
283
+ DoCopy:
284
+ ld a,32
285
+ di
286
+ out (#99),a
287
+ ld a,17 + 128
288
+ out (#99),a
289
+ ld c,#9B
290
+ VDPready:
291
+ ld a,2
292
+ di
293
+ out (#99),a ; select s#2
294
+ ld a,15 + 128
295
+ out (#99),a
296
+ in a,(#99)
297
+ rra
298
+ ld a,0 ; back to s#0, enable ints
299
+ out (#99),a
300
+ ld a,15 + 128
301
+ ei
302
+ out (#99),a ; loop if vdp not ready (CE)
303
+ jp c,VDPready
304
+ outi ; 15x OUTI
305
+ outi ; (faster than OTIR)
306
+ outi
307
+ outi
308
+ outi
309
+ outi
310
+ outi
311
+ outi
312
+ outi
313
+ outi
314
+ outi
315
+ outi
316
+ outi
317
+ outi
318
+ outi
319
+ ret
320
+ ```
321
+
322
+ ## Setting the palette
323
+
324
+ Setting a new VDP palette is a rather easy thing to do. First you have to set the palette pointer in r#16, usually it is set to 0, and then you can write your palette values to port #9A. The palette pointer automatically increments, and loops to 0 again when it reaches the last palette entry. By the way, please note that in screen 8 the palette can’t be used for sprites.
325
+
326
+ Here is an example SetPalette routine. The OTIR could be unrolled to OUTIs if you really need the additional speed (for example on a screensplit).
327
+
328
+ ```assembler
329
+ ;
330
+ ; Set the palette to the one HL points to...
331
+ ; Modifies: AF, BC, HL (=updated)
332
+ ; Enables the interrupts.
333
+ ;
334
+ SetPalette:
335
+ xor a ; set p#pointer to zero.
336
+ di
337
+ out (#99),a
338
+ ld a,16+128
339
+ ei
340
+ out (#99),a
341
+ ld bc,#209A ; out 32x to port #9A
342
+ otir
343
+ ret
344
+ ```
345
+
346
+ ## Programming example
347
+
348
+ This is a small example of a short program which combines most techniques. Do with it whatever you want, look at it, try it out, ignore it... ^_^. It isn’t exactly the summum of speed and optimized code, but ahwell... It will do.
349
+
350
+ ```assembler
351
+ ;
352
+ ; Is supposed to run in screen 5, so you should make a small BASIC loader,
353
+ ; or call the CHMOD BIOS routine.
354
+ ;
355
+ DoExampleCopy:
356
+ xor a ; set vram write base address
357
+ ld hl,#8000 ; to 1st byte of page 1...
358
+ call SetVDP_Write
359
+
360
+ ld a,#88 ; use color 8 (red)
361
+ FillL1:
362
+ ld c,8 ; fill 1st 8 lines of page 1
363
+ FillL2:
364
+ ld b,128 ;
365
+ out (#98),a ; could also have been done with
366
+ djnz FillL2 ; a vdp command (probably faster)
367
+ dec c ; (and could also use a fast loop)
368
+ jp nz,FillL1
369
+
370
+ ld hl,COPYBLOCK ; execute the copy
371
+ call DoCopy
372
+ ret
373
+
374
+ COPYBLOCK:
375
+ db 0,0,0,1
376
+ db 0,0,0,0
377
+ db 8,0,8,0
378
+ db 0,0,#D0 ; HMMM
379
+
380
+ ; As an alternate notation, you might actually prefer the following:
381
+ ;
382
+ ; dw #0000,#0100
383
+ ; dw #0000,#0000
384
+ ; dw #0008,#0008
385
+ ; db 0,0,#D0
386
+ ```
387
+
388
+ ## Source
389
+
390
+ https://map.grauw.nl/articles/vdp_tut.php
@@ -0,0 +1,166 @@
1
+ # Screensplit programming guide
2
+
3
+ Programming screensplits can be tricky, even more so if you want them to work on the different types of MSX computers and CPU speeds. Therefore this article was compiled, so that programmers can easily find detailed information about all ups and downs, buts, and possibilities and impossibilities of screensplits. If you have a suggestion, please mail us about it!
4
+
5
+ About the measurements, those of HR in particular: I have been pointed to some errors and accuracy improvements (some bordering the obvious, argh -_-;;). So don't take them for granted yet, I'll create new measurements. The rest still stands, ofcourse ;p. Update coming (somewhat) soon!
6
+
7
+ - [What is a screensplit?](#what-is-a-screensplit)
8
+ - [What are HBLANK and VBLANK?](#what-are-hblank-and-vblank)
9
+ - [How accurate is this article?](#how-accurate-is-this-article)
10
+ - [How accurate is the HBLANK (HR) bit in s#2?](#how-accurate-is-the-hblank-hr-bit-in-s2)
11
+ - [When does the VDP line interrupt (FH) start?](#when-does-the-vdp-line-interrupt-fh-start)
12
+ - [Which splits are 'seamless'?](#which-splits-are-seamless)
13
+ - [How to make a tidy screen split with a blank line?](#how-to-make-a-tidy-screen-split-with-a-blank-line)
14
+
15
+ - [Tip: Split timing](#tip-split-timing)
16
+ - [Tip: Screensplit on MSX1](#tip-screensplit-on-msx1)
17
+ - [Tip: Spritesplit](#tip-spritesplit)
18
+ - [Tip: Scrollsplit (using r#23)](#tip-scrollsplit-using-r23)
19
+
20
+ ## What is a screensplit?
21
+
22
+ A screensplit can be made by changing certain attributes of the VDP while it is still displaying. The VDP will immediately reflect the changes on what it's displaying, and with that you can achieve some marvellous things you otherwise won't be able to do with the v9938. For example, it is possible to let the VDP show page 0 on the first half of the screen, and page 1 on the second half. This is called a pagesplit. There are also other kinds of splits, for example a palettesplit (so you can use 32+ colors in screen 5), a modesplit (screen 4 for the play area, screen 5 for the score), a spritesplit (show more than 32 sprites), a scrollsplit, etc.
23
+
24
+ The v9938 has a feature which can set a flag when it arrives at a specified line, or even generate an interrupt. This is done by setting the split line in r#19, and then checking the FH flag in s#1. If you enable the E1 bit in r#0 the VDP will generate an interrupt.
25
+
26
+ ## What are HBLANK and VBLANK?
27
+
28
+ The screen as it displays on your screen has borders around it, which are rendered in 1 single border color. During this border rendering the VDP is either in Vertical Blanking or Horizontal Blanking state. The borders on the left and right are rendered during HBLANK, which only lasts a short while, and the borders on the bottom and top are rendered during the VBLANK period, which lasts relatively long. For details on how long they take exactly, refer to appendix 7 of the v9938 application manual.
29
+
30
+ Whether the VDP is in VBLANK or HBLANK state can be found out by checking the VR and HR bits in status register 2 (bits 6 and 5). For screen split programming, the HBLANK period is particularly important. The bits are set to 1 during the blanking period.
31
+
32
+ ## How accurate is this article?
33
+
34
+ I tested the statements made in this article with a number of carefully programmed and optimized programs on an NMS8245 with a v9958 videochip, at aswell 7MHz as 3.5MHz (logically, 7MHz gave me the most exact readings). If you want to verify my findings, download the [split accuracy tests](https://map.grauw.nl/downloads/articles/splittests.lzh).
35
+
36
+ ## How accurate is the HBLANK (HR) bit in s#2?
37
+
38
+ I have been pointed to some errors and accuracy improvements (some bordering the obvious, argh -_-;;). So don't take them for granted, I'll create new tests. It seems that HR is pretty accurate after all. It is 1 during HBLANK. I commented out the incorrect part of the original text for the time being. If you must read it, you can check the source of this page.
39
+
40
+ Example code to poll for the end of HBLANK, assumes disabled interrupts and s#2 to be set:
41
+
42
+ ```assembler
43
+ Poll_1:
44
+ in a,(#99) ; wait until start of HBLANK
45
+ and %00100000
46
+ jp nz,Poll_1
47
+ Poll_2:
48
+ in a,(#99) ; wait until end of HBLANK
49
+ and %00100000
50
+ jp z,Poll_2
51
+ ```
52
+
53
+ ## When does the VDP line interrupt (FH) start?
54
+
55
+ The VDP lineinterrupt is linked to the FH bit, and the interrupt occurs when the FH bit is set. The FH bit will be set at the exact beginning of the line in r#19 + 1, so that's the line after the line set. If register 19 contains the value 99, the lineinterrupt will occur at the utter left of line 100, inside the left border (which is about halfway the horizontal blanking period).
56
+
57
+ Note about the v9958: if you scroll the screen horizontally, the split will occur a few pixels later. However this is really negligible, it is probably the VDP starting displaying the line a couple of pixels later, depending on the H0-H2 bits of the scroll register.
58
+
59
+ When using interrupts the interrupt initialization will also take some time, and in practice the VDP will already be displaying the line by the time the actual split code is executed. Even a the minimal setup, a JP at address #38 and starting with a PUSH AF, already takes a rough ten pixels extra. Also, in order to make the actual split occur as fast as possible, you will probably have a little code which sets up the needed data, perhaps using some self-modifying code or a memory area in combination with indirect register access and OUTI's. This code does not take the same amount of time on each processor, on a 3.5MHz MSX it may take 3 lines, but on a 7MHz one only 2, and on a R800 even 1. In other words, unless an MSX at 3.5MHz (worst case) only needs 1 line for its initialization you can't really expect the VDP to have arrived at line 103 at the end of the CPU's initialization. Because of that it is often a good idea to poll for a new linesplit at line 103, to make sure the split executes on the same line with every CPU.
60
+
61
+ Example code for polling FH, assumes disabled interrupts and s#1 to be set:
62
+
63
+ ```assembler
64
+ ld a,(VDP+23) ; set split line
65
+ add a,SPLITLINE
66
+ out (#99),a
67
+ ld a,19+128
68
+ out (#99),a
69
+ nop ; don't access too fast
70
+ Poll:
71
+ in a,(#99) ; poll until line reached, also clears FH bit
72
+ rra
73
+ jp nc,Poll
74
+ ```
75
+
76
+ Ofcourse, when not using this code in an interrupt routine you shouldn't keep the interrupts disabled in the polling loop all the time.
77
+
78
+ ## Which splits are 'seamless'?
79
+
80
+ First of all, what is seamless? With seamless I am referring to the screen splits whose effect gets delayed by the VDP until the end of the current line, and therefore they always look pretty. As far as I can tell, there are two kinds of splits which are 'seamless', those are screen mode splits (does not include setting new table values) and screen blank splits using bit 6 of r#1. Although the screen mode splits themself are 'seamless', because the table base address registers aren't you still need to put a little effort in it. Often you'll only have to update r#0, r#1 (the mode registers) and r#2 (the pattern name table, the only one used in splits with screens 5 and up), which by the way makes it particularly fit for indirect register access. Might come in handy when you just need that one touch of extra speed.
81
+
82
+ Anyways, as I said before, the mode bits themselves are seamless, but r#2 is not, so you still need to find a means to update r#2 in an invisible manner. The first option you should consider is arranging your data so, that you don't need to change the r#2 value at all. When splitting between mode 4 and mode 5 for example, use %00011111 as the r#2 value. In screen 5 this means base address #0000 (page 0), while in screen 4 this means base address #7C00. In screen 5 the #7C00 area is located in the VBLANK area past line 212, so it won't be visible there.
83
+
84
+ If that doesn't work out there are still a number of other options. It is easiest to insert a clean blanked line (depending on your background color, usually black) as described below. Then there is the option of adapting the images on screen to the split instead of the other way around. In other words, add a line which looks the same in both screen modes, or in case of for example a palette split, a line which hasn't got any of the palette colors which you're changing in it.
85
+
86
+ Only if you are really out of luck and there's no other acceptable option there's the last option: using the HR bit. Again, because it's requires processor-dependant timing this is absolutely not my favorite solution. Anyways, if you wait for HR to indicate HBLANK, then execute the mode split, which requires 2 VDP register updates - should be enough to get you in the *real* HBLANK period (and because of speed limiters in the current faster MSX models register updates use about the same processor time). After that you should be able to update one or two table entries before HBLANK ends again.
87
+
88
+ ## How to make a tidy screen split with a blank line?
89
+
90
+ This is in my opinion the best way to have a screensplit. It looks very tidy, it is easy to program and doesn't require any 'special' (read: difficult or processor-dependant) timing. Basically, there will just be a black line at the spot of the split, but you won't have to put that line in your images, although you ofcourse have to keep it into account when drawing them and designing your screen's layout.
91
+
92
+ During the split you can basically do everything without it becoming visible, per line there is the time to set values in 4 VDP registers, or 8, if you're able to use indirect register access, update 8 palette entries, etc. If you do more register updates a 2nd line will be added... it might be that on a 7MHz MSX it only needs 1 line while it needs 2 on a 3.5MHz MSX - you'll have to take that into account in your design, although consecutive OUT instructions to the VDP execute at pretty much the same speed on all existing MSX CPU's. If you are updating the palette but only the first couple of colours are used on the line after the split, you can make the blank line end before you're done, and update the rest of the palette colors on the next 'normal' line.
93
+
94
+ The 'blank split' can be made by first selecting status register 2, then polling for HBLANK (!HR) and after that for HR. This ensures you you are not in a HBLANK period. Now you can disable your screen with bit 6 of r#1 and now you'll have to wait for the end of the next HBLANK first before the black line takes effect. So, poll !HR and then HR again (or perhaps use FH?). From now on the screen is disabled, and you can execute the necessary split instructions. End the split sequence by enabling the screen again. The screen will enable at the end of the current line, you won't need to do any more polling.
95
+
96
+ Some example code, which assumes the interrupts are disabled, and doesn't switch back the status register afterwards:
97
+
98
+ ```assembler
99
+ ;
100
+ ; A macro definition which waits until the end of the next/current HBLANK...
101
+ ;
102
+ Wait_HBLANK:
103
+ MACRO ; (this macro is Compass-formatted)
104
+ WHL1_@sym:
105
+ in a,(#99) ; wait until start of HBLANK
106
+ and %00100000
107
+ jp nz,WHL1_@sym
108
+ WHL2_@sym:
109
+ in a,(#99) ; wait until end of HBLANK
110
+ and %00100000
111
+ jp z,WHL2_@sym
112
+ ENDM
113
+ ```
114
+
115
+ ```assembler
116
+ ;
117
+ ; The routine performing the screensplit
118
+ ;
119
+ Split:
120
+ ld a,2
121
+ out (#99),a
122
+ ld a,15+128
123
+ out (#99),a
124
+ nop
125
+ Wait_HBLANK
126
+ ld a,(VDP+1) ; disable screen (reset bit 6)
127
+ and %10111111
128
+ out (#99),a
129
+ ld a,1+128
130
+ out (#99),a
131
+ nop
132
+ Wait_HBLANK
133
+
134
+ ... ; do your split stuff
135
+
136
+ ld a,(VDP+1)
137
+ out (#99),a
138
+ ld a,1+128
139
+ out (#99),a
140
+ ```
141
+
142
+ ## Tip: Split timing
143
+
144
+ One should take the fact that an MSX doesn't necessarily run at 3.5MHz in consideration as much as possible. A lot of people have 7MHz builtin, a turboR in R800 mode runs even faster, and who knows there are people using even faster processors in their MSX-es in the future. In other words, test at 3.5MHz (worst case), but if possible also on a higher speed, and whenever possible, don't let your timing depend on the processor's instruction execution speed. Synchronize your split on the FH (in s#1) and HR (in s#2) bits, and if possible use a split's 'seamless' properties. This is quite possible and although there may be some extra work in it, you are ensured that your program works correctly on all current and future MSX computers. Granted, the horizontal screensplit in the scope part of Unknown Reality would be really hard to code without processor-dependant timing (although they might/could have used the deviation of the HR bit)... But I doubt you will ever use a split like that.
145
+
146
+ ## Tip: Screensplit on MSX1
147
+
148
+ Another method of linesplits, which can even be used on MSX1 (!), is to use sprites and the 5th sprite/collision bits. The basic idea is that you can put 5 sprites on 1 line, or 2 overlapping sprites at the spot where you want the split to occur, and then poll the appropriate bit in the status register. As soon as the line where the 5th sprite is on is encountered, this bit is set, so you can time quite precisely with this. The collision bit is even set at the spot where the sprites collide, although apparantly it is a bit unstable, it might just be precise enough to achieve horizontal splits!! (being splits which run vertically but are timed horizontally :)). You will then need to respond very fast though, which you might be able to do by using in f,(c) instructions and then checking the parity. Will require some clever coding ^_^. The 5th sprite and collision bits are reset after they're read. Thanks to Eli-Jean Leyssens aka Kanima for the tip. The trick will be used in his -at the time of writing- upcoming demo for MSX1...
149
+
150
+ ## Tip: Spritesplit
151
+
152
+ If you are creating a spritesplit, you should keep the entries of the sprites which are displayed on the line of the split unchanged in both the pattern and the attribute table (or duplicate them if you change table addresses). This MUST be done, making a spritesplit will otherwise be a hard if not impossible job, which will certainly not work on every type of MSX. Oh, and remember, the spritedata is read one line BEFORE the actual display line. So if you change sprite tables on the split line, the first line with sprites will be one line lower.Thanks to Patriek Lesparre for the info.
153
+
154
+ ## Tip: Scrollsplit (using r#23)
155
+
156
+ If you have a split and are changing the vertical scrollregister (r#23) on it, then you should always re-set the splitline (r#19). This because the splitline is calculated from line 0 in the VRAM, and not from line 0 of the screen. In order to set the splitline to the 'screenline' it's easiest to simply add the value of r#23 to it.
157
+
158
+ Next you should also consider the possibility that the VDP coincidentally arrives at the 'wrong' line right between setting the r#23 and r#19 registers. Sure, the change is small, somewhat processor-dependant, and can depending on the case be ruled out entirely, but it's best to keep it into account anyways. Therefore, read out the value of s#1 before you enable the interrupts again, which will reset the linesplit bit in FH. You should only leave it out if you are absolutely sure a lineinterrupt won't occur inbetween.
159
+
160
+ In case you don't want to re-set the splitline, for example because it will be set back at a later time during the building of the screen, that's also possible, but then you should disable the splitinterrupt E1 during that period (reset bit 4 of r#0), and when r#23 is set back, read out s#1, and only then enable E1 again. This will prevent the occurrance of an additional lineinterrupt which will otherwise be reasonably likely to occur.
161
+
162
+ ~Grauw
163
+
164
+ ## Source
165
+
166
+ https://map.grauw.nl/articles/split_guide.php