git.y1.nz

gbdk-2020

GameBoy Development Kit
download: https://git.y1.nz/archives/gbdk.tar.gz
README | Files | Log | Refs | LICENSE

commit 790d96d2f35ed84651ee584bc1e7a9cadb84fd25
parent 04bac46997dc11ec2721c33c25847adf1a3daec9
Author: bbbbbr <bbbbbr@users.noreply.github.com>
Date:   Mon, 11 Aug 2025 17:23:19 -0700

Merge pull request #808 from bbbbbr/megaduck/printer_related_changes

Duck: Add single pass printer example
Diffstat:
Agbdk-lib/examples/megaduck/laptop_printer/Makefile99+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Agbdk-lib/examples/megaduck/laptop_printer/Makefile.targets54++++++++++++++++++++++++++++++++++++++++++++++++++++++
Agbdk-lib/examples/megaduck/laptop_printer/Readme.md23+++++++++++++++++++++++
Agbdk-lib/examples/megaduck/laptop_printer/src/main.c123+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Agbdk-lib/examples/megaduck/laptop_printer/src/megaduck_printer.c182+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Agbdk-lib/examples/megaduck/laptop_printer/src/megaduck_printer.h46++++++++++++++++++++++++++++++++++++++++++++++
Agbdk-lib/examples/megaduck/laptop_printer/src/megaduck_printscreen.c131+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Agbdk-lib/examples/megaduck/laptop_printer/src/megaduck_printscreen.h18++++++++++++++++++
8 files changed, 676 insertions(+), 0 deletions(-)

diff --git a/gbdk-lib/examples/megaduck/laptop_printer/Makefile b/gbdk-lib/examples/megaduck/laptop_printer/Makefile @@ -0,0 +1,99 @@ +# If you move this project you can change the directory +# to match your GBDK root directory (ex: GBDK_HOME = "C:/GBDK/" +ifndef GBDK_HOME + GBDK_HOME = ../../../ +endif + +LCC = $(GBDK_HOME)/bin/lcc + +# Set platforms to build here, spaced separated. (These are in the separate Makefile.targets) +# They can also be built/cleaned individually: "make gg" and "make gg-clean" +# Possible are: gb gbc pocket megaduck sms gg +TARGETS= megaduck # gb pocket megaduck sms gg nes + +# Configure platform specific LCC flags here: +LCCFLAGS_gb = # -Wl-yt0x1B # Set an MBC for banking (1B-ROM+MBC5+RAM+BATT) +LCCFLAGS_pocket = # -Wl-yt0x1B # Usually the same as required for .gb +LCCFLAGS_duck = +LCCFLAGS_gbc = # -Wl-yt0x1B -Wm-yc # Same as .gb with: -Wm-yc (gb & gbc) or Wm-yC (gbc exclusive) +LCCFLAGS_sms = +LCCFLAGS_gg = +LCCFLAGS_nes = + +LCCFLAGS += $(LCCFLAGS_$(EXT)) # This adds the current platform specific LCC Flags + +LCCFLAGS += -Wl-j # -Wm-yS -Wm-yoA -Wm-ya4 -autobank -Wb-ext=.rel -Wb-v # MBC + Autobanking related flags + +LCCFLAGS += -Wf-MMD -Wf-Wp-MP # Header file dependency output (-MMD) for Makefile use + per-header Phony rules (-MP) +CFLAGS += -Wf-MMD -Wf-Wp-MP # Header file dependency output (-MMD) for Makefile use + per-header Phony rules (-MP) + +# GBDK_DEBUG = ON +ifdef GBDK_DEBUG + LCCFLAGS += -debug -v +endif + + +# You can set the name of the ROM file here +PROJECTNAME = megaduck_printer + +# EXT?=gb # Only sets extension to default (game boy .gb) if not populated +SRCDIR = src +OBJDIR = obj/$(EXT) +RESDIR = res +BINDIR = build/$(EXT) +MKDIRS = $(OBJDIR) $(BINDIR) # See bottom of Makefile for directory auto-creation + +BINS = $(OBJDIR)/$(PROJECTNAME).$(EXT) +CSOURCES = $(foreach dir,$(SRCDIR),$(notdir $(wildcard $(dir)/*.c))) $(foreach dir,$(RESDIR),$(notdir $(wildcard $(dir)/*.c))) + +ASMSOURCES = $(foreach dir,$(SRCDIR),$(notdir $(wildcard $(dir)/*.s))) +OBJS = $(CSOURCES:%.c=$(OBJDIR)/%.o) $(ASMSOURCES:%.s=$(OBJDIR)/%.o) + +# Dependencies (using output from -Wf-MMD -Wf-Wp-MP) +DEPS = $(OBJS:%.o=%.d) + +-include $(DEPS) + +# Builds all targets sequentially +all: $(TARGETS) + +test: + echo $(CSOURCES) + + +# Compile .c files in "src/" to .o object files +$(OBJDIR)/%.o: $(SRCDIR)/%.c + $(LCC) $(CFLAGS) -c -o $@ $< + +# Compile .c files in "res/" to .o object files +$(OBJDIR)/%.o: $(RESDIR)/%.c + $(LCC) $(CFLAGS) -c -o $@ $< + +# Compile .s assembly files in "src/" to .o object files +$(OBJDIR)/%.o: $(SRCDIR)/%.s + $(LCC) $(CFLAGS) -c -o $@ $< + +# If needed, compile .c files in "src/" to .s assembly files +# (not required if .c is compiled directly to .o) +$(OBJDIR)/%.s: $(SRCDIR)/%.c + $(LCC) $(CFLAGS) -S -o $@ $< + +# Link the compiled object files into a .gb ROM file +$(BINS): $(OBJS) + $(LCC) $(LCCFLAGS) $(CFLAGS) -o $(BINDIR)/$(PROJECTNAME).$(EXT) $(OBJS) + +clean: + @echo Cleaning + @for target in $(TARGETS); do \ + $(MAKE) $$target-clean; \ + done + +# Include available build targets +include Makefile.targets + + +# create necessary directories after Makefile is parsed but before build +# info prevents the command from being pasted into the makefile +ifneq ($(strip $(EXT)),) # Only make the directories if EXT has been set by a target +$(info $(shell mkdir -p $(MKDIRS))) +endif diff --git a/gbdk-lib/examples/megaduck/laptop_printer/Makefile.targets b/gbdk-lib/examples/megaduck/laptop_printer/Makefile.targets @@ -0,0 +1,54 @@ + +# Platform specific flags for compiling (only populate if they're both present) +ifneq ($(strip $(PORT)),) +ifneq ($(strip $(PLAT)),) +CFLAGS += -m$(PORT):$(PLAT) +endif +endif + +# Called by the individual targets below to build a ROM +build-target: $(BINS) + +clean-target: + rm -rf $(OBJDIR) + rm -rf $(BINDIR) + +gb-clean: + ${MAKE} clean-target EXT=gb +gb: + ${MAKE} build-target PORT=sm83 PLAT=gb EXT=gb + + +gbc-clean: + ${MAKE} clean-target EXT=gbc +gbc: + ${MAKE} build-target PORT=sm83 PLAT=gb EXT=gbc + + +pocket-clean: + ${MAKE} clean-target EXT=pocket +pocket: + ${MAKE} build-target PORT=sm83 PLAT=ap EXT=pocket + + +megaduck-clean: + ${MAKE} clean-target EXT=duck +megaduck: + ${MAKE} build-target PORT=sm83 PLAT=duck EXT=duck + + +sms-clean: + ${MAKE} clean-target EXT=sms +sms: + ${MAKE} build-target PORT=z80 PLAT=sms EXT=sms + + +gg-clean: + ${MAKE} clean-target EXT=gg +gg: + ${MAKE} build-target PORT=z80 PLAT=gg EXT=gg + +nes-clean: + ${MAKE} clean-target EXT=nes +nes: + ${MAKE} build-target PORT=mos6502 PLAT=nes EXT=nes diff --git a/gbdk-lib/examples/megaduck/laptop_printer/Readme.md b/gbdk-lib/examples/megaduck/laptop_printer/Readme.md @@ -0,0 +1,23 @@ +## GBDK example for the Mega Duck Laptop +How to interface with the printer made for the Mega Duck Laptop +models ("Super QuiQue" and "Super Junior Computer"). + +- Initializing the external controller connected over the serial link port +- Checking whether the printer is connected and which type +- Printing the screen to the printer +- Printing blank rows to the printer (to scroll the paper) + +## Printer Types +There are two types of Mega Duck printer, single pass and double pass. + +- The single pass monochrome model is the standard Mega Duck JC-510 thermal printer which connects to the DB-26 "Printer" port. +- The double pass model 3-color model (identified in the System ROM disassembly) is most likely the JC-502 "Drucker Interface" which connects to DB-15 "External Connector" port and allows interfacing with standard (1990's) parallel printers. + +Printing example support for the double pass model is untested +on hardware at present and so not included in the `main` branch. +For it's example code see the `save_2_pass_printer` branch. + +## Code structure +There are two separate files for the Mega Duck printer in this project. +- `megaduck_printer.c` contains the core interface code for querying the printer, sending tile data to it and transforming (rotate and flip) tiles to the format expected by the print er. +- `megaduck_printscreen.c` contains example code for sending a "print screen" of the console screen to the printer. diff --git a/gbdk-lib/examples/megaduck/laptop_printer/src/main.c b/gbdk-lib/examples/megaduck/laptop_printer/src/main.c @@ -0,0 +1,123 @@ +#include <gbdk/platform.h> +#include <gbdk/console.h> +#include <gb/isr.h> + +#include <stdint.h> +#include <stdbool.h> +#include <stdio.h> + +#include <duck/laptop_io.h> + +#include "megaduck_printer.h" +#include "megaduck_printscreen.h" + +uint8_t printer_type; + +const uint8_t bg_tile[] = { + 0x00, 0x00, + 0x00, 0x00, + 0xFF, 0x00, + 0xFF, 0x00, + 0x00, 0xFF, + 0x00, 0xFF, + 0xFF, 0xFF, + 0xFF, 0xFF, }; + + +uint8_t printer_query(void) { + + printf("Printer Query\n"); + + uint8_t printer_query_result = duck_io_printer_query(); + printf("Printer Result %hx\n", printer_query_result); + + // return (printer_query_result & DUCK_IO_PRINTER_INIT_OK); + return printer_query_result; +} + + +static bool duck_laptop_and_printer_init(void) { + + bool megaduck_laptop_init_ok = duck_io_laptop_init(); + if (!megaduck_laptop_init_ok) { + // If laptop hardware is not present then there isn't anything + // for this program to do, so just idle in a loop + printf("Laptop not detected\n" + "or Failed to Initialize\n"); + return false; + } + + // Otherwise laptop init succeeded + printf("Laptop Detected!\n"); + + // Check to see if the printer was connected at startup, and if so what model + printer_type = duck_io_printer_last_status(); + // Fix up printer status == 3 to be printer type 1, sort of a hack + if (printer_type == DUCK_IO_PRINTER_MAYBE_BUSY) printer_type = DUCK_IO_PRINTER_TYPE_1_PASS; + + // If that failed, try a re-query of the printer + if (printer_type == DUCK_IO_PRINTER_FAIL) { + + printf("No printer at startup\n" + " re-querying...\n"); + if (duck_io_printer_query() == DUCK_IO_PRINTER_FAIL) { + printf("Printer not found\n"); + return false; + } + } + + // Otherwise printer init succeeded + printf("Printer Detected!\n"); + if (printer_type == DUCK_IO_PRINTER_TYPE_1_PASS) + printf("- Single Pass model\n"); + else + printf("- Double Pass model\n"); + + return true; +} + + +void main(void) { + + uint8_t gamepad = 0; + uint8_t gamepad_last = 0; + + SPRITES_8x8; + SHOW_SPRITES; + SHOW_BKG; + printf("Initializing..\n"); + + // Set tile as last tile + set_bkg_data(255, 1, bg_tile); + fill_bkg_rect(0, DEVICE_SCREEN_HEIGHT - 4, DEVICE_SCREEN_WIDTH - 1, DEVICE_SCREEN_HEIGHT - 1, 255); + + // Stop here if no printer detected + if (duck_laptop_and_printer_init() == false) { + // Optionally take action if no printer is detected + } + + printf("\nPress:\n" + "* START to print\n" + "* SELECT requery\n" + "* A print blank row\n"); + + while(1) { + vsync(); + gamepad_last = gamepad; + gamepad = joypad(); + + // Send command to print the screen if START is pressed + switch (gamepad) { + case J_START: + printf("Starting print...\n"); + // uint8_t printer_type = printer_query(); + bool print_job_status = duck_print_screen(); + printf("Finished print, status: %hu\n", print_job_status); + // Wait until START is released before continuing + waitpadup(); + break; + case J_A: duck_print_blank_row(); waitpadup(); break; + case J_SELECT: printer_query(); waitpadup(); break; + } + } +} diff --git a/gbdk-lib/examples/megaduck/laptop_printer/src/megaduck_printer.c b/gbdk-lib/examples/megaduck/laptop_printer/src/megaduck_printer.c @@ -0,0 +1,182 @@ +#include <gbdk/platform.h> +#include <stdint.h> +#include <stdbool.h> + +#include <duck/laptop_io.h> + +#include "megaduck_printer.h" + +uint8_t tile_row_buffer[DEVICE_SCREEN_WIDTH * BYTES_PER_PRINTER_TILE]; + +// For sending a bulk printer command to megaduck printer +// +// Expects duck_io_tx_buf to be pre-loaded with payload +// +// The System ROM uses an infinite retry which would block +// program execution forever if the printer failed. Instead +// 10x has been determined via trial and error as a reasonable +// number of retries. +static bool print_send_command_and_buffer_delay_1msec_10x_retry(uint8_t command) { + + uint8_t retry = 10u; + while (retry--) { + bool result = duck_io_send_cmd_and_buffer(command); + delay(1); + if (result == true) return true; + } + return false; +} + + +// Transforming tile data for Printer use +// +// SCREEN ---> PRINTER +// +// This (1bpp) input tile Should be transformed to the following PRINTER formatted output: +// +// *BITS* (X) Tile bytes (X) +// 7 ___ 0 Bytes 0 ___ 7 +// | +// 0 X....... = [0] = 0x80 0 X....... +// (Y)| X....... = [1] = 0x80 (Y)| X....... +// | X....... = [2] = 0x80 * | X....... +// b | X....... = [3] = 0x80 B | X....... +// y | ........ = [4] = 0x00 I | ........ +// t | ........ = [5] = 0x00 T | ........ +// e | ........ = [6] = 0x00 S | ........ +// s 7 .XXXXXXX = [7] = 0x7F * 7 .XXXXXXX +// [0 ... 7] <- Tile Bytes <- {0xF0, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01} +// +// The first ROW in above The first COLUMN in above +// represents the byte 0x80 represents the byte 0xF0 + + +// Converts one plane (i.e. monochrome, not 4 shades of grey) of +// an 8x8 Game Boy format tile for printing on the Mega Duck Printer. +void duck_printer_convert_tile(uint8_t * p_out_buf, uint8_t * p_tile_buf) { + + // Clear printer tile + for (uint8_t c = 0u; c < BYTES_PER_PRINTER_TILE; c++) { + p_out_buf[c] = 0u; + } + + // Transform tile bytes into printable row buffer bytes + // Tile must get flipped horizontally and rotated -90 degrees + // + // Note the +2 increment for tile row, skipping the interleaved higher bit plane + // + // For each tile row byte take the X axis bits representing pixels + // and transform them into column oriented bits spread across 8 bytes + // + uint8_t out_bit = 0x80u; // X axis bit to set in the output for corresponding input + for (uint8_t tile_row = 0u; tile_row < BYTES_PER_VRAM_TILE; tile_row += 2u) { + uint8_t tile_byte = p_tile_buf[tile_row]; + + // Scan X axis of tile Left to right + uint8_t tile_bit = 0x80u; + for (uint8_t out_col = 0; out_col < TILE_HEIGHT; out_col++) { + + if (tile_byte & tile_bit) p_out_buf[out_col] |= out_bit; + tile_bit >>= 1; + } + out_bit >>= 1; + } +} + + +// Converts one both planes (i.e. 4 shades of grey) of an 8x8 Game Boy format +// tile into partially dithered monochrome for printing on the Mega Duck Printer. +// +// Color 0: always white +// Color 1: white or black based on checkerboard dither pattern +// Color 2 or 3: always black +void duck_printer_convert_tile_dithered(uint8_t * p_out_buf, uint8_t * p_tile_buf) { + + // Clear printer tile + for (uint8_t c = 0u; c < BYTES_PER_PRINTER_TILE; c++) { + p_out_buf[c] = 0u; + } + + // Transform tile bytes into printable row buffer bytes + // Tile must get flipped horizontally and rotated -90 degrees + // + // For each tile row byte take the X axis bits representing pixels + // and transform them into column oriented bits spread across 8 bytes + // + uint8_t out_bit = 0x80u; // X axis bit to set in the output for corresponding input + uint8_t dither = 0xAAu; // Dither pattern + for (uint8_t tile_row = 0u; tile_row < BYTES_PER_VRAM_TILE; tile_row += 2u) { + uint8_t tile_byte0 = p_tile_buf[tile_row]; + uint8_t tile_byte1 = p_tile_buf[tile_row+1]; + + // LSByte first, Scan X axis Left to right + uint8_t tile_bit = 0x80u; + for (uint8_t out_col = 0; out_col < TILE_HEIGHT; out_col++) { + + if (tile_byte1 & tile_bit) { + p_out_buf[out_col] |= out_bit;; // Color 2 or 3 = always on + } else if ((tile_byte0 & dither) & tile_bit) { + p_out_buf[out_col] |= out_bit; // Color 1 enabled based on checkerboard dither pattern + } + + tile_bit >>= 1; + } + // Flip dither pattern for next source tile row + dither = ~dither; + out_bit >>= 1; + } +} + + +// Send a prepared 20 x 8x8 row of tile data in duck_io_tx_buf[] +// to the Mega Duck printer in 1bpp format. +// +// The tile data should already be transformed using one of the +// duck_printer_convert_tile_...() functions. +bool duck_printer_send_tile_row_1pass(void) { + + uint8_t * p_row_buffer = tile_row_buffer; + + // Send 13 x 12 byte packets with row data + duck_io_tx_buf_len = PRINTER_LEN_12_ROW_DATA; + for (uint8_t packet = 0u; packet < PRINTER_1_PASS_ROW_NUM_PACKETS; packet++) { + + for (uint8_t c = 0u; c < (duck_io_tx_buf_len); c++) + duck_io_tx_buf[c] = *p_row_buffer++; + + if (!print_send_command_and_buffer_delay_1msec_10x_retry(DUCK_IO_CMD_PRINT_SEND_BYTES)) { + // This delay seems to fix periodic skipped tile glitching + delay(PRINT_DELAY_BETWEEN_ROWS_1000MSEC); + + return false; // Fail out if there was a problem + } + } + + uint8_t txbyte; + // Now send remaining bulk non-packetized data (unclear why transmit methods are split) + for (txbyte = 0u; txbyte < PRINTER_1_PASS_ROW_NUM_BULK_DATA_BYTES; txbyte++) { + duck_io_read_byte_with_msecs_timeout(PRINTER_1_PASS_BULK_ACK_TIMEOUT_100MSEC); + duck_io_send_byte(*p_row_buffer++); + } + + // Send last four bulk bytes after end of tile data, unclear what they are for + for (txbyte = 0u; txbyte < PRINTER_1_PASS_ROW_NUM_BULK_UNKNOWN_BYTES; txbyte++) { + duck_io_read_byte_with_msecs_timeout(PRINTER_1_PASS_BULK_ACK_TIMEOUT_100MSEC); + duck_io_send_byte(0x00); + } + + // Wait for last bulk data ACK (with 1msec delay for unknown reason) + duck_io_read_byte_with_msecs_timeout(PRINT_ROW_END_ACK_WAIT_TIMEOUT_200MSEC); + + // End of row: wait for Carriage Return confirmation ACK from the printer + // System ROM doesn't seem to care about the return value, so we won't either for now + duck_io_read_byte_with_msecs_timeout(PRINT_ROW_END_ACK_WAIT_TIMEOUT_200MSEC); + + // This delay seems to fix periodic skipped tile glitching + // as well as peripheral controller asic lockup and cpu reset + // if the keyboard is polled too soon after the end of a + // print row is sent. + delay(PRINT_DELAY_BETWEEN_ROWS_1000MSEC); + + return true; // Success +} diff --git a/gbdk-lib/examples/megaduck/laptop_printer/src/megaduck_printer.h b/gbdk-lib/examples/megaduck/laptop_printer/src/megaduck_printer.h @@ -0,0 +1,46 @@ +#include <gbdk/platform.h> +#include <stdint.h> +#include <stdbool.h> + +#ifndef _MEGADUCK_PRINTER_H +#define _MEGADUCK_PRINTER_H + +// // Thermal Printer related +#define PRINTER_CARRIAGE_RETURN 0x0D // Return print head to start of 8 pixel high row +#define PRINTER_LINE_FEED 0x0A // Feed printer paper to next 8 pixel high row (there are two print passes per-row [to print different greys], so LF only happens every other row of printing) +#define PRINTER_LEN_END_ROW_DATA_SZ 4 // 4 data bytes +#define PRINTER_LEN_5_END_ROW_CR 5 // 5 bytes (4 data bytes + CR + LF) +#define PRINTER_LEN_6_END_ROW_CRLF 6 // 6 bytes (4 data bytes + CR + LF) +#define PRINTER_LEN_12_ROW_DATA 12 // 12 data bytes +#define PRINTER_CR_IDX (PRINTER_LEN_5_END_ROW_CR - 1) // Byte number 5 +#define PRINTER_LF_IDX (PRINTER_LEN_6_END_ROW_CRLF - 1) // Byte number 6 + +#define PRINTER_2_PASS_ROW_NUM_PACKETS 14u +#define PRINTER_2_PASS_ROW_LAST_PACKET (PRINTER_2_PASS_ROW_NUM_PACKETS - 1u) + +#define PRINTER_1_PASS_ROW_NUM_PACKETS 4u +#define PRINTER_1_PASS_ROW_NUM_BULK_BYTES 118u +#define PRINTER_1_PASS_ROW_NUM_BULK_DATA_BYTES 112u +#define PRINTER_1_PASS_ROW_NUM_BULK_UNKNOWN_BYTES (PRINTER_1_PASS_ROW_NUM_BULK_BYTES - PRINTER_1_PASS_ROW_NUM_BULK_DATA_BYTES) +#define PRINTER_1_PASS_BULK_ACK_TIMEOUT_100MSEC 100u + +#define PRINT_ROW_END_ACK_WAIT_TIMEOUT_200MSEC 200u // Presumably waiting for a carriage return confirmation ACK from the printer +#define PRINT_DELAY_BETWEEN_ROWS_1000MSEC 1000u // Waiting for the print head to return to the start of the line and buffer to be cleared + + +#define BYTES_PER_PRINTER_TILE 8u +#define BYTES_PER_VRAM_TILE 16u +#define TILE_HEIGHT 8u +#define TILE_WIDTH 8u + +// Buffer to store an entire queued tile row for printing +extern uint8_t tile_row_buffer[DEVICE_SCREEN_WIDTH * BYTES_PER_PRINTER_TILE]; + + +bool duck_printer_send_tile_row_1pass(void); +void duck_printer_convert_tile(uint8_t * p_out_buf, uint8_t * p_tile_buf); +void duck_printer_convert_tile_dithered(uint8_t * p_out_buf, uint8_t * p_tile_buf); +bool duck_printer_send_tile_row_1pass(void); + + +#endif // _MEGADUCK_PRINTER_H diff --git a/gbdk-lib/examples/megaduck/laptop_printer/src/megaduck_printscreen.c b/gbdk-lib/examples/megaduck/laptop_printer/src/megaduck_printscreen.c @@ -0,0 +1,131 @@ +#include <gbdk/platform.h> +#include <stdint.h> +#include <stdbool.h> + +#include <duck/laptop_io.h> + +#include "megaduck_printer.h" +#include "megaduck_printscreen.h" + +static void printscreen_prepare_tile_row(uint8_t row, uint8_t tile_bitplane_offset); + + +// Currently unknown: +// - Single pass printer probably does not support variable image width +// - Double pass printer might, since it has explicit Carriage Return and Line Feed commands, but it's not verified +// +// So for the time being require full screen image width +// +// The Duck Printer mechanical Carriage Return + Line Feed process takes about +// 500 msec for the print head to travel back to the start of the line. +// +// After that there is about a 600 msec period before the printer head +// starts moving. The ASIC between the CPU and the printer may be +// buffering printer data during that time so it can stream it out +// with the right timing. +// +// So giving the printer ~1000 msec between tile rows is needed +// to avoid printing glitches. The duration was determined through +// trial and error. +// +// The same ~1000 msec delay should be present after the last +// tile row is sent to allow the printer to finish processing +// that row before sending other commands. For example if this +// isn't done and a keyboard poll is sent immediately after +// the last tile row, then the peripheral controller seems to +// trigger a cpu reset. +// +bool duck_print_screen(void) { + + bool return_status = true; + + // Check for printer connectivity + uint8_t printer_type = duck_io_printer_query(); + // Fix up printer status == 3 to be printer type 1, sort of a hack + if (printer_type == DUCK_IO_PRINTER_MAYBE_BUSY) printer_type = DUCK_IO_PRINTER_TYPE_1_PASS; + if (printer_type != DUCK_IO_PRINTER_TYPE_1_PASS) return false; + + // Turn off VBlank interrupt during printing to avoid disruptions when + // streaming the tile row data + uint8_t int_enables_saved = IE_REG; + set_interrupts(IE_REG & ~VBL_IFLAG); + + // Starting with a blank row (like system rom does) avoids a glitch where + // a tile is skipped somewhere in the very first row printed + duck_print_blank_row(); + + // Send the tile data row by row + for (uint8_t map_row = 0; map_row < DEVICE_SCREEN_HEIGHT; map_row++) { + printscreen_prepare_tile_row(map_row, BITPLANE_BOTH); + return_status = duck_printer_send_tile_row_1pass(); + if (return_status == false) break; + } + + // Print up to N blank rows to scroll the printed result up + // past the printer tear off position + if (return_status) { + for (uint8_t blank_row=0u; blank_row < PRINT_NUM_BLANK_ROWS_AT_END; blank_row++) { + duck_print_blank_row(); + } + } + + // Restore VBlank interrupt + set_interrupts(int_enables_saved); + + return return_status; +} + + +// Prints a blank tile row by filling the pattern data with all zeros +// and then printing a row +bool duck_print_blank_row(void) { + + // Fill print buffer with zero's + uint8_t * p_buf = tile_row_buffer; + for (uint8_t c = 0u; c < (DEVICE_SCREEN_WIDTH * BYTES_PER_PRINTER_TILE); c++) { + *p_buf++ = 0x00u; + } + + return duck_printer_send_tile_row_1pass(); +} + + +// Prepares a tile row from the contents of screen vram +static void printscreen_prepare_tile_row(uint8_t row, uint8_t tile_bitplane_offset) { + + uint8_t tile_buffer[BYTES_PER_VRAM_TILE]; + uint8_t * p_row_buffer = tile_row_buffer; + + bool use_win_data = (((row * TILE_HEIGHT) >= WY_REG) && ( LCDC_REG & LCDCF_WINON)); + uint8_t col = 0; + + if (!use_win_data) { + // Add Scroll offset to closest tile (if rendering BG instead of Window) + row += (SCY_REG / TILE_HEIGHT); + col += (SCX_REG / TILE_WIDTH); + } + + // Loop through tile columns for the current tile row + for (uint8_t tile = 0u; tile < DEVICE_SCREEN_WIDTH; tile++) { + + uint8_t tile_id; + // Get the Tile ID from the map then copy it's tile pattern data into a buffer + if (use_win_data) { + // Read window data instead if it's enabled and visible + tile_id = get_win_tile_xy((col + tile) & (DEVICE_SCREEN_BUFFER_HEIGHT - 1), + (row - (WY_REG / TILE_HEIGHT)) & (DEVICE_SCREEN_BUFFER_HEIGHT - 1)); + } else { + // Otherwise use the normal BG + tile_id = get_bkg_tile_xy((col + tile) & (DEVICE_SCREEN_BUFFER_HEIGHT - 1), + row & (DEVICE_SCREEN_BUFFER_HEIGHT - 1)); + } + get_bkg_data(tile_id, 1u, tile_buffer); + + // Mirror, Rotate -90 degrees and reduce tile to 1bpp + if (tile_bitplane_offset == BITPLANE_BOTH) + duck_printer_convert_tile_dithered(p_row_buffer, tile_buffer); + else + duck_printer_convert_tile(p_row_buffer, tile_buffer + tile_bitplane_offset); + p_row_buffer += BYTES_PER_PRINTER_TILE; + } +} diff --git a/gbdk-lib/examples/megaduck/laptop_printer/src/megaduck_printscreen.h b/gbdk-lib/examples/megaduck/laptop_printer/src/megaduck_printscreen.h @@ -0,0 +1,18 @@ +#include <gbdk/platform.h> +#include <stdint.h> +#include <stdbool.h> + +#ifndef _MEGADUCK_PRINTSCREEN_H +#define _MEGADUCK_PRINTSCREEN_H + +#define PRINT_NUM_BLANK_ROWS_AT_END 3u + +// Bitplane offsets into tile pattern data for 2 pass printing +#define BITPLANE_0 0 +#define BITPLANE_1 1 +#define BITPLANE_BOTH 2 + +bool duck_print_screen(void); +bool duck_print_blank_row(void); + +#endif // _MEGADUCK_PRINTSCREEN_H

This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.