GameBoy (Color) port of the GTA San Andreas arcade game Duality
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

gbprinter.c 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  1. /*
  2. * gbprinter.c
  3. * Duality
  4. *
  5. * Based on the gbprinter example from gbdk-2020:
  6. * https://github.com/gbdk-2020/gbdk-2020/tree/develop/gbdk-lib/examples/gb/gbprinter
  7. *
  8. * Copyright (C) 2025 Thomas Buck <thomas@xythobuz.de>
  9. *
  10. * This program is free software: you can redistribute it and/or modify
  11. * it under the terms of the GNU General Public License as published by
  12. * the Free Software Foundation, either version 3 of the License, or
  13. * (at your option) any later version.
  14. *
  15. * This program is distributed in the hope that it will be useful,
  16. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. * GNU General Public License for more details.
  19. *
  20. * See <http://www.gnu.org/licenses/>.
  21. */
  22. #include <gbdk/platform.h>
  23. #include <gbdk/emu_debug.h>
  24. #include <stdio.h>
  25. #include <string.h>
  26. #include "gb/gb.h"
  27. #include "gb/hardware.h"
  28. #include "input.h"
  29. #include "gbprinter.h"
  30. BANKREF(gbprinter)
  31. /** Width of the printed image in tiles
  32. */
  33. #define PRN_TILE_WIDTH 20
  34. #define PRN_LOW(A) ((A) & 0xFF)
  35. #define PRN_HIGH(A) ((A) >> 8)
  36. /** 0x88,0x33 are mandatory first bytes to initialise a communication with printer
  37. * Any command sequence begins by these
  38. */
  39. #define PRN_MAGIC 0x3388
  40. #define PRN_LE(A) PRN_LOW(A),PRN_HIGH(A)
  41. /** magic number that is sent in the reply packet by the printer before the status byte
  42. */
  43. #define PRN_MAGIC_DETECT 0x81
  44. /** INIT command is mandatory to initialize communication protocol with the printer
  45. * Two consecutive linked commands must never be more than 150 ms apart except the INIT command which is valid at least 10 seconds
  46. */
  47. #define PRN_CMD_INIT 0x01
  48. /** PRINT command
  49. * Contains the palette, margins, number of prints and printing intensity
  50. */
  51. #define PRN_CMD_PRINT 0x02
  52. /** DATA command
  53. * Can be any length between 0 and 640 bytes.
  54. * DATA command with lenght 0 triggers PRN_STATUS_FULL and is mandatory before print command
  55. */
  56. #define PRN_CMD_DATA 0x04
  57. /** BREAK command
  58. * Not very usefull but exists (see Game Boy Programming Manual)
  59. */
  60. #define PRN_CMD_BREAK 0x08
  61. /** STATUS command
  62. * Used to check status bits
  63. * Maybe be used alone before an INIT command to check physical connection with printer
  64. * Resets PRN_STATUS_UNTRAN
  65. */
  66. #define PRN_CMD_STATUS 0x0F
  67. /** Palette format: the bits, grouped two by two, give the printing color of the encoded pixel value
  68. * for the default palette 0xE4 = 0b11100100 = [3 2 1 0]
  69. * Any value is valid, which means that 1 to 4 color images are possible
  70. * 0x00 acts the same as 0xE4 for the printer
  71. */
  72. #define PRN_PALETTE_NORMAL 0b11100100u
  73. #define PRN_PALETTE_INV 0b00011011u
  74. /** Don't use margins
  75. */
  76. #define PRN_NO_MARGINS 0x00
  77. /** Exposure: 0x40 is default value, values from 0x80 to 0xFF act as 0x40
  78. * Determines the time used by the printer head to heat the thermal paper
  79. */
  80. #define PRN_EXPOSURE_LIGHT 0x00
  81. #define PRN_EXPOSURE_DEFAULT 0x40
  82. #define PRN_EXPOSURE_DARK 0x7F
  83. /** Battery too low
  84. */
  85. #define PRN_STATUS_LOWBAT 0x80
  86. /** Error not specified according to the Game Boy Programming manual
  87. */
  88. #define PRN_STATUS_ER2 0x40
  89. /** Paper jam (abnormal motor operation)
  90. */
  91. #define PRN_STATUS_ER1 0x20
  92. /** Packet error (but not checksum error)
  93. */
  94. #define PRN_STATUS_ER0 0x10
  95. /** Unprocessed data present in printer memory
  96. * Allows to verify that printer got some data in memory with correct checksum
  97. * is resetted by STATUS command
  98. */
  99. #define PRN_STATUS_UNTRAN 0x08
  100. /** status data ready, mandatory to allow printing
  101. * is triggered by DATA command with lenght 0
  102. */
  103. #define PRN_STATUS_FULL 0x04
  104. /** Message sent by the printer while physically printing
  105. */
  106. #define PRN_STATUS_BUSY 0x02
  107. /** The received packet has a ckecksum error
  108. */
  109. #define PRN_STATUS_SUM 0x01
  110. #define PRN_STATUS_MASK_ERRORS 0xF0
  111. #define PRN_STATUS_MASK_ANY 0xFF
  112. #define PRN_SECONDS(A) ((A)*60)
  113. #define PRN_MAX_PROGRESS 8
  114. #define PRN_STATUS_CANCELLED PRN_STATUS_ER2
  115. #define REINIT_SEIKO
  116. #define START_TRANSFER 0x81
  117. #define PRN_BUSY_TIMEOUT PRN_SECONDS(2)
  118. #define PRN_COMPLETION_TIMEOUT PRN_SECONDS(20)
  119. #define PRN_SEIKO_RESET_TIMEOUT 10
  120. #define PRN_FINAL_MARGIN 0x03
  121. typedef struct start_print_pkt_s {
  122. uint16_t magic;
  123. uint16_t command;
  124. uint16_t length;
  125. uint8_t print;
  126. uint8_t margins;
  127. uint8_t palette;
  128. uint8_t exposure;
  129. uint16_t crc;
  130. uint16_t trail;
  131. } start_print_pkt_t;
  132. static const uint8_t PRN_PKT_INIT[] = {
  133. PRN_LE(PRN_MAGIC), PRN_LE(PRN_CMD_INIT), PRN_LE(0), PRN_LE(0x01), PRN_LE(0)
  134. };
  135. static const uint8_t PRN_PKT_STATUS[] = {
  136. PRN_LE(PRN_MAGIC), PRN_LE(PRN_CMD_STATUS), PRN_LE(0), PRN_LE(0x0F), PRN_LE(0)
  137. };
  138. static const uint8_t PRN_PKT_EOF[] = {
  139. PRN_LE(PRN_MAGIC), PRN_LE(PRN_CMD_DATA), PRN_LE(0), PRN_LE(0x04), PRN_LE(0)
  140. };
  141. static const uint8_t PRN_PKT_CANCEL[] = {
  142. PRN_LE(PRN_MAGIC), PRN_LE(PRN_CMD_BREAK), PRN_LE(0), PRN_LE(0x01), PRN_LE(0)
  143. };
  144. static start_print_pkt_t PRN_PKT_START = {
  145. .magic = PRN_MAGIC, .command = PRN_CMD_PRINT,
  146. .length = 4, .print = TRUE,
  147. .margins = 0, .palette = PRN_PALETTE_NORMAL, .exposure = PRN_EXPOSURE_DARK,
  148. .crc = 0, .trail = 0
  149. };
  150. static uint16_t printer_status;
  151. static uint8_t printer_tile_num;
  152. static inline void gbprinter_set_print_params(uint8_t margins, uint8_t palette, uint8_t exposure) {
  153. PRN_PKT_START.crc = ((PRN_CMD_PRINT + 0x04u + 0x01u)
  154. + (PRN_PKT_START.margins = margins)
  155. + (PRN_PKT_START.palette = palette)
  156. + (PRN_PKT_START.exposure = exposure));
  157. }
  158. static uint8_t printer_send_receive(uint8_t b) {
  159. SB_REG = b;
  160. SC_REG = START_TRANSFER;
  161. while (SC_REG & 0x80);
  162. return SB_REG;
  163. }
  164. static uint8_t printer_send_byte(uint8_t b) {
  165. return (uint8_t)(printer_status = ((printer_status << 8) | printer_send_receive(b)));
  166. }
  167. static uint8_t printer_send_command(const uint8_t *command, uint8_t length) {
  168. uint8_t index = 0;
  169. while (index++ < length) printer_send_byte(*command++);
  170. return ((uint8_t)(printer_status >> 8) == PRN_MAGIC_DETECT) ? (uint8_t)printer_status : PRN_STATUS_MASK_ERRORS;
  171. }
  172. #define PRINTER_SEND_COMMAND(CMD) printer_send_command((const uint8_t *)&(CMD), sizeof(CMD))
  173. static uint8_t printer_print_tile(const uint8_t *tiledata) {
  174. static const uint8_t PRINT_TILE[] = { 0x88,0x33,0x04,0x00,0x80,0x02 };
  175. static uint16_t printer_CRC;
  176. if (printer_tile_num == 0) {
  177. const uint8_t * data = PRINT_TILE;
  178. for (uint8_t i = sizeof(PRINT_TILE); i != 0; i--) printer_send_receive(*data++);
  179. printer_CRC = 0x04 + 0x80 + 0x02;
  180. }
  181. for(uint8_t i = 0x10; i != 0; i--, tiledata++) {
  182. printer_CRC += *tiledata;
  183. printer_send_receive(*tiledata);
  184. }
  185. if (++printer_tile_num == 40) {
  186. printer_send_receive((uint8_t)printer_CRC);
  187. printer_send_receive((uint8_t)(printer_CRC >> 8));
  188. printer_send_receive(0x00);
  189. printer_send_receive(0x00);
  190. printer_CRC = printer_tile_num = 0;
  191. return TRUE;
  192. }
  193. return FALSE;
  194. }
  195. static inline void printer_init(void) {
  196. printer_tile_num = 0;
  197. PRINTER_SEND_COMMAND(PRN_PKT_INIT);
  198. }
  199. uint8_t printer_check_cancel(void) {
  200. key_read();
  201. return key_pressed(J_B);
  202. }
  203. static uint8_t printer_wait(uint16_t timeout, uint8_t mask, uint8_t value) {
  204. uint8_t error;
  205. while (((error = PRINTER_SEND_COMMAND(PRN_PKT_STATUS)) & mask) != value) {
  206. if (printer_check_cancel()) {
  207. PRINTER_SEND_COMMAND(PRN_PKT_CANCEL);
  208. return PRN_STATUS_CANCELLED;
  209. }
  210. if (timeout-- == 0) return PRN_STATUS_MASK_ERRORS;
  211. if (error & PRN_STATUS_MASK_ERRORS) break;
  212. vsync();
  213. }
  214. return error;
  215. }
  216. uint8_t gbprinter_detect(uint8_t delay) BANKED {
  217. printer_init();
  218. uint8_t r = printer_wait(delay, PRN_STATUS_MASK_ANY, PRN_STATUS_OK);
  219. #ifdef DEBUG
  220. EMU_printf("%s: %hu\n", __func__, (uint8_t)r);
  221. #endif // DEBUG
  222. return r;
  223. }
  224. uint8_t gbprinter_print_image(const uint8_t *image_map, const uint8_t *image,
  225. int8_t pos_x, uint8_t width, uint8_t height,
  226. uint8_t done) BANKED {
  227. uint8_t tile_data[16];
  228. uint8_t rows = ((height + 1) >> 1) << 1;
  229. uint8_t pkt_count = 0;
  230. if ((rows >> 1) == 0) return PRN_STATUS_OK;
  231. printer_tile_num = 0;
  232. for (uint8_t y = 0; y < rows; y++) {
  233. for (int16_t x = 0; x < PRN_TILE_WIDTH; x++) {
  234. #ifdef DEBUG
  235. EMU_printf("%s: %hu %i\n", __func__, (uint8_t)y, (int16_t)x);
  236. #endif // DEBUG
  237. // overlay the picture tile if in range
  238. if ((y < height) && (x >= pos_x) && (x < (pos_x + width))) {
  239. uint8_t tile = image_map[(y * width) + (x - pos_x)];
  240. memcpy(tile_data, image + ((uint16_t)tile << 4), sizeof(tile_data));
  241. } else {
  242. memset(tile_data, 0, sizeof(tile_data));
  243. }
  244. // print the resulting tile
  245. if (printer_print_tile(tile_data)) {
  246. pkt_count++;
  247. if (printer_check_cancel()) {
  248. PRINTER_SEND_COMMAND(PRN_PKT_CANCEL);
  249. return PRN_STATUS_CANCELLED;
  250. }
  251. }
  252. if (pkt_count == 9) {
  253. pkt_count = 0;
  254. PRINTER_SEND_COMMAND(PRN_PKT_EOF);
  255. // setup margin if last packet
  256. gbprinter_set_print_params((y == (rows - 1)) ? PRN_FINAL_MARGIN : PRN_NO_MARGINS,
  257. PRN_PALETTE_NORMAL, PRN_EXPOSURE_DARK);
  258. PRINTER_SEND_COMMAND(PRN_PKT_START);
  259. // query printer status
  260. uint8_t error = printer_wait(PRN_BUSY_TIMEOUT, PRN_STATUS_BUSY, PRN_STATUS_BUSY);
  261. if (error & PRN_STATUS_MASK_ERRORS) {
  262. return error;
  263. }
  264. error = printer_wait(PRN_COMPLETION_TIMEOUT, PRN_STATUS_BUSY, 0);
  265. if (error & PRN_STATUS_MASK_ERRORS) {
  266. return error;
  267. }
  268. #ifdef REINIT_SEIKO
  269. // reinit printer (required by Seiko?)
  270. if (y < (rows - 1)) {
  271. PRINTER_SEND_COMMAND(PRN_PKT_INIT);
  272. error = printer_wait(PRN_SEIKO_RESET_TIMEOUT, PRN_STATUS_MASK_ANY, PRN_STATUS_OK);
  273. if (error) {
  274. return error;
  275. }
  276. }
  277. #endif
  278. }
  279. }
  280. }
  281. if (pkt_count && done) {
  282. PRINTER_SEND_COMMAND(PRN_PKT_EOF);
  283. // setup printing if required
  284. gbprinter_set_print_params(PRN_FINAL_MARGIN, PRN_PALETTE_NORMAL, PRN_EXPOSURE_DARK);
  285. PRINTER_SEND_COMMAND(PRN_PKT_START);
  286. // query printer status
  287. uint8_t error = printer_wait(PRN_BUSY_TIMEOUT, PRN_STATUS_BUSY, PRN_STATUS_BUSY);
  288. if (error & PRN_STATUS_MASK_ERRORS) {
  289. return error;
  290. }
  291. error = printer_wait(PRN_COMPLETION_TIMEOUT, PRN_STATUS_BUSY, 0);
  292. if (error & PRN_STATUS_MASK_ERRORS) {
  293. return error;
  294. }
  295. }
  296. return PRINTER_SEND_COMMAND(PRN_PKT_STATUS);
  297. }
  298. uint8_t gbprinter_screenshot(uint8_t win) BANKED {
  299. static uint8_t map_buff[2 * DEVICE_SCREEN_WIDTH];
  300. static uint8_t tile_buff[2 * DEVICE_SCREEN_WIDTH * 16];
  301. printer_init();
  302. printer_status = 0x00;
  303. uint8_t r = PRN_STATUS_OK;
  304. for (int y = 0; y < DEVICE_SCREEN_HEIGHT; y += 2) {
  305. for (int y2 = 0; y2 < 2; y2++) {
  306. for (int x = 0; x < DEVICE_SCREEN_WIDTH; x++) {
  307. uint8_t tile = win ? get_win_tile_xy(x, y + y2) : get_bkg_tile_xy(x, y + y2);
  308. map_buff[x + (y2 * DEVICE_SCREEN_WIDTH)] = (x + (y2 * DEVICE_SCREEN_WIDTH));
  309. win ? get_win_data(tile, 1, tile_buff + ((x + (y2 * DEVICE_SCREEN_WIDTH)) * 16))
  310. : get_bkg_data(tile, 1, tile_buff + ((x + (y2 * DEVICE_SCREEN_WIDTH)) * 16));
  311. }
  312. // black out rows we have sent, to indicate transfer progress
  313. win ? fill_win_rect(0, y + y2, DEVICE_SCREEN_WIDTH, 1, 0)
  314. : fill_bkg_rect(0, y + y2, DEVICE_SCREEN_WIDTH, 1, 0);
  315. }
  316. r = gbprinter_print_image(map_buff, tile_buff, 0, DEVICE_SCREEN_WIDTH, 2,
  317. (y == (DEVICE_SCREEN_HEIGHT - 2)) ? 1 : 0);
  318. if ((r & ~PRN_STATUS_UNTRAN) != PRN_STATUS_OK) {
  319. break;
  320. }
  321. }
  322. return r;
  323. }