gbdk-2020 | GameBoy Development Kit |
| download: https://git.y1.nz/archives/gbdk.tar.gz | |
| README | Files | Log | Refs | LICENSE |
commit 21b1f9bc50bcd991b876f8d24aebea1abfefe902 parent 18a41e34a68129e50ea17986978579a2ebdc2219 Author: Toxa <untoxa@mail.ru> Date: Tue, 20 Feb 2024 20:09:02 +0300 EXAMPLES: Game Boy Printer example Diffstat:
| A | gbdk-lib/examples/gb/gbprinter/Makefile | 101 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | gbdk-lib/examples/gb/gbprinter/res/scene00001.png | 0 | |
| A | gbdk-lib/examples/gb/gbprinter/src/gbprinter.c | 156 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | gbdk-lib/examples/gb/gbprinter/src/gbprinter.h | 144 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | gbdk-lib/examples/gb/gbprinter/src/print_example.c | 33 | +++++++++++++++++++++++++++++++++ |
5 files changed, 434 insertions(+), 0 deletions(-)
diff --git a/gbdk-lib/examples/gb/gbprinter/Makefile b/gbdk-lib/examples/gb/gbprinter/Makefile @@ -0,0 +1,101 @@ +# +# A Makefile that compiles all .c and .s files in "src" and "res" +# subdirectories and places the output in a "obj" subdirectory +# + +# 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 +PNG2ASSET = $(GBDK_HOME)bin/png2asset + +# GBDK_DEBUG = ON +ifdef GBDK_DEBUG + LCCFLAGS += -debug -v +endif + + +# Add directory where image gets converted into (obj/) +# So they can be included with "#include <res/somefile.h>" +LCCFLAGS += -I$(OBJDIR) + +# Make the ROM CGB compatible (but not exclusive) +# LCCFLAGS += -Wm-yc + +# You can set the name of the .gb ROM file here +PROJECTNAME = print_example + +SRCDIR = src +OBJDIR = obj +RESOBJSRC = obj/res +RESDIR = res +MKDIRS = $(OBJDIR) $(BINDIR) $(RESOBJSRC) # See bottom of Makefile for directory auto-creation + +BINS = $(OBJDIR)/$(PROJECTNAME).gb + +# For png2asset: converting source pngs -> .c -> .o +IMGPNGS = $(foreach dir,$(RESDIR),$(notdir $(wildcard $(dir)/*.png))) +IMGSOURCES = $(IMGPNGS:%.png=$(RESOBJSRC)/%.c) +IMGOBJS = $(IMGSOURCES:$(RESOBJSRC)/%.c=$(OBJDIR)/%.o) + +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) + +all: $(BINS) + +compile.bat: Makefile + @echo "REM Automatically generated from Makefile" > compile.bat + @make -sn | sed y/\\//\\\\/ | sed s/mkdir\ -p\/mkdir\/ | grep -v make >> compile.bat + +# Use png2asset to convert the png into C formatted metasprite data +# -keep_duplicate_tiles : Don't remove duplicate tiles +# -map : Use "map style" output, not metasprite +# -tiles_only : Only keep tiles, no map (since APA mode expects tiles in linear order) +# -bpp 2 : Use 2bpp output +# -c ... : Set C output file +# Convert metasprite .pngs in res/ -> .c files in obj/<platform ext>/src/ +$(RESOBJSRC)/%.c: $(RESDIR)/%.png + $(PNG2ASSET) $< -keep_duplicate_tiles -map -bpp2 -c $@ + +# Compile the pngs that were converted to .c files +# .c files in obj/res/ -> .o files in obj/ +$(OBJDIR)/%.o: $(RESOBJSRC)/%.c + $(LCC) $(LCCFLAGS) $(CFLAGS) -c -o $@ $< + +# Compile .c files in "src/" to .o object files +$(OBJDIR)/%.o: $(SRCDIR)/%.c + $(LCC) $(LCCFLAGS) -c -o $@ $< + +# Compile .c files in "res/" to .o object files +$(OBJDIR)/%.o: $(RESDIR)/%.c + $(LCC) $(LCCFLAGS) $(CFLAGS) -c -o $@ $< + +# Compile .s assembly files in "src/" to .o object files +$(OBJDIR)/%.o: $(SRCDIR)/%.s + $(LCC) $(LCCFLAGS) -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) $(LCCFLAGS) -S -o $@ $< + +# Convert images first so they're available when compiling the main sources +$(OBJS): $(IMGOBJS) + +# Link the compiled object files into a .gb ROM file +$(BINS): $(OBJS) + $(LCC) $(LCCFLAGS) -o $(BINS) $(IMGOBJS) $(OBJS) + +clean: + rm -f $(OBJDIR)/*.* + rm -f $(RESOBJSRC)/*.* + + +# create necessary directories after Makefile is parsed but before build +# info prevents the command from being pasted into the makefile +$(info $(shell mkdir -p $(MKDIRS))) + diff --git a/gbdk-lib/examples/gb/gbprinter/res/scene00001.png b/gbdk-lib/examples/gb/gbprinter/res/scene00001.png Binary files differ. diff --git a/gbdk-lib/examples/gb/gbprinter/src/gbprinter.c b/gbdk-lib/examples/gb/gbprinter/src/gbprinter.c @@ -0,0 +1,156 @@ +#include <gbdk/platform.h> +#include <stdint.h> +#include <string.h> +#include <stdbool.h> + +#include "gbprinter.h" + +#define REINIT_SEIKO + +#define START_TRANSFER 0x81 +#define PRN_BUSY_TIMEOUT PRN_SECONDS(2) +#define PRN_COMPLETION_TIMEOUT PRN_SECONDS(20) +#define PRN_SEIKO_RESET_TIMEOUT 10 + +#define PRN_FINAL_MARGIN 0x03 + +static const uint8_t PRN_PKT_INIT[] = { PRN_LE(PRN_MAGIC), PRN_LE(PRN_CMD_INIT), PRN_LE(0), PRN_LE(0x01), PRN_LE(0) }; +static const uint8_t PRN_PKT_STATUS[] = { PRN_LE(PRN_MAGIC), PRN_LE(PRN_CMD_STATUS), PRN_LE(0), PRN_LE(0x0F), PRN_LE(0) }; +static const uint8_t PRN_PKT_EOF[] = { PRN_LE(PRN_MAGIC), PRN_LE(PRN_CMD_DATA), PRN_LE(0), PRN_LE(0x04), PRN_LE(0) }; +static const uint8_t PRN_PKT_CANCEL[] = { PRN_LE(PRN_MAGIC), PRN_LE(PRN_CMD_BREAK), PRN_LE(0), PRN_LE(0x01), PRN_LE(0) }; + +start_print_pkt_t PRN_PKT_START = { + .magic = PRN_MAGIC, .command = PRN_CMD_PRINT, .length = 4, + .print = TRUE, .margins = 0, .palette = PRN_PALETTE_NORMAL, .exposure = PRN_EXPOSURE_DARK, + .crc = 0, .trail = 0 +}; + +static uint16_t printer_status; +static uint8_t printer_tile_num; + +uint8_t printer_send_receive(uint8_t b) { + SB_REG = b; + SC_REG = START_TRANSFER; + while (SC_REG & 0x80); + return SB_REG; +} + +uint8_t printer_send_byte(uint8_t b) { + return (uint8_t)(printer_status = ((printer_status << 8) | printer_send_receive(b))); +} + +uint8_t printer_send_command(const uint8_t *command, uint8_t length) { + uint8_t index = 0; + while (index++ < length) printer_send_byte(*command++); + return ((uint8_t)(printer_status >> 8) == PRN_MAGIC_DETECT) ? (uint8_t)printer_status : PRN_STATUS_MASK_ERRORS; +} +#define PRINTER_SEND_COMMAND(CMD) printer_send_command((const uint8_t *)&(CMD), sizeof(CMD)) + +uint8_t printer_print_tile(const uint8_t *tiledata) { + static const uint8_t PRINT_TILE[] = { 0x88,0x33,0x04,0x00,0x80,0x02 }; + static uint16_t printer_CRC; + if (printer_tile_num == 0) { + const uint8_t * data = PRINT_TILE; + for (uint8_t i = sizeof(PRINT_TILE); i != 0; i--) printer_send_receive(*data++); + printer_CRC = 0x04 + 0x80 + 0x02; + } + for(uint8_t i = 0x10; i != 0; i--, tiledata++) { + printer_CRC += *tiledata; + printer_send_receive(*tiledata); + } + if (++printer_tile_num == 40) { + printer_send_receive((uint8_t)printer_CRC); + printer_send_receive((uint8_t)(printer_CRC >> 8)); + printer_send_receive(0x00); + printer_send_receive(0x00); + printer_CRC = printer_tile_num = 0; + return TRUE; + } + return FALSE; +} + +inline void printer_init(void) { + printer_tile_num = 0; + PRINTER_SEND_COMMAND(PRN_PKT_INIT); +} + +extern bool printer_check_cancel(void); + +uint8_t printer_wait(uint16_t timeout, uint8_t mask, uint8_t value) { + uint8_t error; + while (((error = PRINTER_SEND_COMMAND(PRN_PKT_STATUS)) & mask) != value) { + if (printer_check_cancel()) { + PRINTER_SEND_COMMAND(PRN_PKT_CANCEL); + return PRN_STATUS_CANCELLED; + } + if (timeout-- == 0) return PRN_STATUS_MASK_ERRORS; + if (error & PRN_STATUS_MASK_ERRORS) break; + vsync(); + } + return error; +} + +uint8_t gbprinter_detect(uint8_t delay) { + printer_init(); + return printer_wait(delay, PRN_STATUS_MASK_ANY, PRN_STATUS_OK); +} + +uint8_t gbprinter_print_image(const uint8_t * image_map, const uint8_t * image, uint8_t pos_x, uint8_t width, uint8_t height) { + static const uint8_t * img; + static uint8_t error; + + uint8_t tile_data[16], rows = (((height + 1) >> 1) << 1), pkt_count = 0; + + if ((rows >> 1) == 0) return PRN_STATUS_OK; + + img = image; + + printer_tile_num = 0; + + for (uint8_t y = 0; y != rows; y++) { + for (uint8_t x = 0; x != PRN_TILE_WIDTH; x++) { + // overlay the picture tile if in range + if ((y < height) && (x >= pos_x) && (x < (pos_x + width))) { + uint8_t tile = image_map[(y * width) + (x - pos_x)]; + memcpy(tile_data, img + ((uint16_t)tile << 4), sizeof(tile_data)); + } else { + memset(tile_data, 0, sizeof(tile_data)); + } + // print the resulting tile + if (printer_print_tile(tile_data)) { + pkt_count++; + if (printer_check_cancel()) { + PRINTER_SEND_COMMAND(PRN_PKT_CANCEL); + return PRN_STATUS_CANCELLED; + } + } + if (pkt_count == 9) { + pkt_count = 0; + PRINTER_SEND_COMMAND(PRN_PKT_EOF); + // setup margin if last packet + gbprinter_set_print_params((y == (rows - 1)) ? PRN_FINAL_MARGIN : PRN_NO_MARGINS, PRN_PALETTE_NORMAL, PRN_EXPOSURE_DARK); + PRINTER_SEND_COMMAND(PRN_PKT_START); + // query printer status + if ((error = printer_wait(PRN_BUSY_TIMEOUT, PRN_STATUS_BUSY, PRN_STATUS_BUSY)) & PRN_STATUS_MASK_ERRORS) return error; + if ((error = printer_wait(PRN_COMPLETION_TIMEOUT, PRN_STATUS_BUSY, 0)) & PRN_STATUS_MASK_ERRORS) return error; +#ifdef REINIT_SEIKO + // reinit printer (required by Seiko?) + if (y != (rows - 1)) { + PRINTER_SEND_COMMAND(PRN_PKT_INIT); + if (error = printer_wait(PRN_SEIKO_RESET_TIMEOUT, PRN_STATUS_MASK_ANY, PRN_STATUS_OK)) return error; + } +#endif + } + } + } + if (pkt_count) { + PRINTER_SEND_COMMAND(PRN_PKT_EOF); + // setup printing if required + gbprinter_set_print_params(PRN_FINAL_MARGIN, PRN_PALETTE_NORMAL, PRN_EXPOSURE_DARK); + PRINTER_SEND_COMMAND(PRN_PKT_START); + // query printer status + if ((error = printer_wait(PRN_BUSY_TIMEOUT, PRN_STATUS_BUSY, PRN_STATUS_BUSY)) & PRN_STATUS_MASK_ERRORS) return error; + if ((error = printer_wait(PRN_COMPLETION_TIMEOUT, PRN_STATUS_BUSY, 0)) & PRN_STATUS_MASK_ERRORS) return error; + } + return PRINTER_SEND_COMMAND(PRN_PKT_STATUS); +} diff --git a/gbdk-lib/examples/gb/gbprinter/src/gbprinter.h b/gbdk-lib/examples/gb/gbprinter/src/gbprinter.h @@ -0,0 +1,144 @@ +#ifndef __GBPRINTER_H_INCLUDE__ +#define __GBPRINTER_H_INCLUDE__ + +#include <gbdk/platform.h> +#include <stdint.h> + +/** Width of the printed image in tiles +*/ +#define PRN_TILE_WIDTH 20 + +#define PRN_LOW(A) ((A) & 0xFF) +#define PRN_HIGH(A) ((A) >> 8) + +/** 0x88,0x33 are mandatory first bytes to initialise a communication with printer + Any command sequence begins by these +*/ +#define PRN_MAGIC 0x3388 +#define PRN_LE(A) PRN_LOW(A),PRN_HIGH(A) + +/** magic number that is sent in the reply packet by the printer before the status byte + */ +#define PRN_MAGIC_DETECT 0x81 + +/** INIT command is mandatory to initialize communication protocol with the printer + Two consecutive linked commands must never be more than 150 ms apart except the INIT command which is valid at least 10 seconds +*/ +#define PRN_CMD_INIT 0x01 + +/** PRINT command + Contains the palette, margins, number of prints and printing intensity +*/ +#define PRN_CMD_PRINT 0x02 + +/** DATA command + Can be any length between 0 and 640 bytes. + DATA command with lenght 0 triggers PRN_STATUS_FULL and is mandatory before print command +*/ +#define PRN_CMD_DATA 0x04 + +/** BREAK command + Not very usefull but exists (see Game Boy Programming Manual) +*/ +#define PRN_CMD_BREAK 0x08 + +/** STATUS command + Used to check status bits + Maybe be used alone before an INIT command to check physical connection with printer + Resets PRN_STATUS_UNTRAN +*/ +#define PRN_CMD_STATUS 0x0F + +/** Palette format: the bits, grouped two by two, give the printing color of the encoded pixel value + for the default palette 0xE4 = 0b11100100 = [3 2 1 0] + Any value is valid, which means that 1 to 4 color images are possible + 0x00 acts the same as 0xE4 for the printer +*/ +#define PRN_PALETTE_NORMAL 0b11100100u +#define PRN_PALETTE_INV 0b00011011u + +/** Don't use margins +*/ +#define PRN_NO_MARGINS 0x00 + +/** Exposure: 0x40 is default value, values from 0x80 to 0xFF act as 0x40 + Determines the time used by the printer head to heat the thermal paper +*/ +#define PRN_EXPOSURE_LIGHT 0x00 +#define PRN_EXPOSURE_DEFAULT 0x40 +#define PRN_EXPOSURE_DARK 0x7F + +/** Battery too low +*/ +#define PRN_STATUS_LOWBAT 0x80 + +/** Error not specified according to the Game Boy Programming manual +*/ +#define PRN_STATUS_ER2 0x40 + +/** Paper jam (abnormal motor operation) +*/ +#define PRN_STATUS_ER1 0x20 + +/** Packet error (but not checksum error) +*/ +#define PRN_STATUS_ER0 0x10 + +/** Unprocessed data present in printer memory + Allows to verify that printer got some data in memory with correct checksum + is resetted by STATUS command +*/ +#define PRN_STATUS_UNTRAN 0x08 + +/** status data ready, mandatory to allow printing + is triggered by DATA command with lenght 0 +*/ +#define PRN_STATUS_FULL 0x04 + +/** Message sent by the printer while physically printing +*/ +#define PRN_STATUS_BUSY 0x02 + +/** The received packet has a ckecksum error +*/ +#define PRN_STATUS_SUM 0x01 + +/** Everything is fine, printer ready for further transmission +*/ +#define PRN_STATUS_OK 0x00 + +#define PRN_STATUS_MASK_ERRORS 0xF0 +#define PRN_STATUS_MASK_ANY 0xFF + +#define PRN_SECONDS(A) ((A)*60) + +#define PRN_MAX_PROGRESS 8 + +#define PRN_STATUS_CANCELLED PRN_STATUS_ER2 + +#define PRINTER_DETECT_TIMEOUT 10 + +typedef struct start_print_pkt_t { + uint16_t magic; + uint16_t command; + uint16_t length; + uint8_t print; + uint8_t margins; + uint8_t palette; + uint8_t exposure; + uint16_t crc; + uint16_t trail; +} start_print_pkt_t; + +extern uint8_t printer_completion; + +extern start_print_pkt_t PRN_PKT_START; + +inline void gbprinter_set_print_params(uint8_t margins, uint8_t palette, uint8_t exposure) { + PRN_PKT_START.crc = ((PRN_CMD_PRINT + 0x04u + 0x01u) + (PRN_PKT_START.margins = margins) + (PRN_PKT_START.palette = palette) + (PRN_PKT_START.exposure = exposure)); +} + +uint8_t gbprinter_detect(uint8_t delay); +uint8_t gbprinter_print_image(const uint8_t * image_map, const uint8_t * image, uint8_t pos_x, uint8_t width, uint8_t height); + +#endif diff --git a/gbdk-lib/examples/gb/gbprinter/src/print_example.c b/gbdk-lib/examples/gb/gbprinter/src/print_example.c @@ -0,0 +1,32 @@ +#include <gbdk/platform.h> +#include <stdio.h> +#include <stdbool.h> + +#include "gbprinter.h" + +#include "res/scene00001.h" + +bool printer_check_cancel(void) { + static uint8_t keys = 0, old_keys; + old_keys = keys; keys = joypad(); + return (((old_keys ^ keys) & J_B) & (keys & J_B)); +} + +void main(void) { + while(1) { + puts("Press A to print"); + waitpad(J_A); + if (gbprinter_detect(PRINTER_DETECT_TIMEOUT) == PRN_STATUS_OK) { + if (gbprinter_print_image(scene00001_map, scene00001_tiles, + 0, + (scene00001_WIDTH / scene00001_TILE_W), (scene00001_HEIGHT / scene00001_TILE_H)) == PRN_STATUS_OK) { + puts("Printed OK!"); + } else { + puts("Print error!"); + } + } else { + puts("No printer!"); + } + waitpadup(); + } +} +
This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.