├── osd.h ├── doc ├── old_osd.png ├── the_board.jpg └── wire_fpc.jpg ├── images ├── logo.png ├── i_grid.png ├── i_hpos.png ├── i_num0.png ├── i_num1.png ├── i_num2.png ├── i_num3.png ├── i_num4.png ├── i_num5.png ├── i_num6.png ├── i_num7.png ├── i_num8.png ├── i_num9.png ├── i_numA.png ├── i_numB.png ├── i_numC.png ├── i_numD.png ├── i_numE.png ├── i_numF.png ├── i_vpos.png ├── t_grid.png ├── t_hpos.png ├── t_vpos.png ├── i_battery.png ├── i_colors.png ├── i_palette.png ├── t_battery.png ├── t_colors.png ├── t_palette.png ├── i_brightness.png └── t_brightness.png ├── .gitignore ├── makefile ├── config.h ├── gfx.h ├── spicmd.h ├── defs.h ├── imgconv.py ├── readme.md ├── reg.h ├── spicmd.c ├── gfx.c ├── config.c ├── main.c ├── gfx_logo.h └── osd.c /osd.h: -------------------------------------------------------------------------------- 1 | 2 | void osd_init(); 3 | 4 | -------------------------------------------------------------------------------- /doc/old_osd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kgsws/mgb_ips_lcd_osd/HEAD/doc/old_osd.png -------------------------------------------------------------------------------- /images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kgsws/mgb_ips_lcd_osd/HEAD/images/logo.png -------------------------------------------------------------------------------- /doc/the_board.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kgsws/mgb_ips_lcd_osd/HEAD/doc/the_board.jpg -------------------------------------------------------------------------------- /doc/wire_fpc.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kgsws/mgb_ips_lcd_osd/HEAD/doc/wire_fpc.jpg -------------------------------------------------------------------------------- /images/i_grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kgsws/mgb_ips_lcd_osd/HEAD/images/i_grid.png -------------------------------------------------------------------------------- /images/i_hpos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kgsws/mgb_ips_lcd_osd/HEAD/images/i_hpos.png -------------------------------------------------------------------------------- /images/i_num0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kgsws/mgb_ips_lcd_osd/HEAD/images/i_num0.png -------------------------------------------------------------------------------- /images/i_num1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kgsws/mgb_ips_lcd_osd/HEAD/images/i_num1.png -------------------------------------------------------------------------------- /images/i_num2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kgsws/mgb_ips_lcd_osd/HEAD/images/i_num2.png -------------------------------------------------------------------------------- /images/i_num3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kgsws/mgb_ips_lcd_osd/HEAD/images/i_num3.png -------------------------------------------------------------------------------- /images/i_num4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kgsws/mgb_ips_lcd_osd/HEAD/images/i_num4.png -------------------------------------------------------------------------------- /images/i_num5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kgsws/mgb_ips_lcd_osd/HEAD/images/i_num5.png -------------------------------------------------------------------------------- /images/i_num6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kgsws/mgb_ips_lcd_osd/HEAD/images/i_num6.png -------------------------------------------------------------------------------- /images/i_num7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kgsws/mgb_ips_lcd_osd/HEAD/images/i_num7.png -------------------------------------------------------------------------------- /images/i_num8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kgsws/mgb_ips_lcd_osd/HEAD/images/i_num8.png -------------------------------------------------------------------------------- /images/i_num9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kgsws/mgb_ips_lcd_osd/HEAD/images/i_num9.png -------------------------------------------------------------------------------- /images/i_numA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kgsws/mgb_ips_lcd_osd/HEAD/images/i_numA.png -------------------------------------------------------------------------------- /images/i_numB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kgsws/mgb_ips_lcd_osd/HEAD/images/i_numB.png -------------------------------------------------------------------------------- /images/i_numC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kgsws/mgb_ips_lcd_osd/HEAD/images/i_numC.png -------------------------------------------------------------------------------- /images/i_numD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kgsws/mgb_ips_lcd_osd/HEAD/images/i_numD.png -------------------------------------------------------------------------------- /images/i_numE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kgsws/mgb_ips_lcd_osd/HEAD/images/i_numE.png -------------------------------------------------------------------------------- /images/i_numF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kgsws/mgb_ips_lcd_osd/HEAD/images/i_numF.png -------------------------------------------------------------------------------- /images/i_vpos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kgsws/mgb_ips_lcd_osd/HEAD/images/i_vpos.png -------------------------------------------------------------------------------- /images/t_grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kgsws/mgb_ips_lcd_osd/HEAD/images/t_grid.png -------------------------------------------------------------------------------- /images/t_hpos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kgsws/mgb_ips_lcd_osd/HEAD/images/t_hpos.png -------------------------------------------------------------------------------- /images/t_vpos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kgsws/mgb_ips_lcd_osd/HEAD/images/t_vpos.png -------------------------------------------------------------------------------- /images/i_battery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kgsws/mgb_ips_lcd_osd/HEAD/images/i_battery.png -------------------------------------------------------------------------------- /images/i_colors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kgsws/mgb_ips_lcd_osd/HEAD/images/i_colors.png -------------------------------------------------------------------------------- /images/i_palette.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kgsws/mgb_ips_lcd_osd/HEAD/images/i_palette.png -------------------------------------------------------------------------------- /images/t_battery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kgsws/mgb_ips_lcd_osd/HEAD/images/t_battery.png -------------------------------------------------------------------------------- /images/t_colors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kgsws/mgb_ips_lcd_osd/HEAD/images/t_colors.png -------------------------------------------------------------------------------- /images/t_palette.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kgsws/mgb_ips_lcd_osd/HEAD/images/t_palette.png -------------------------------------------------------------------------------- /images/i_brightness.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kgsws/mgb_ips_lcd_osd/HEAD/images/i_brightness.png -------------------------------------------------------------------------------- /images/t_brightness.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kgsws/mgb_ips_lcd_osd/HEAD/images/t_brightness.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.asm 2 | *.ihx 3 | *.lk 4 | *.lst 5 | *.map 6 | *.mem 7 | *.rel 8 | *.rst 9 | *.sym 10 | gfx_osd.h 11 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | TARGET = flash.ihx 2 | CFLAGS = --model-small -I../include 3 | LFLAGS = --code-loc 0x0000 --code-size 0x4000 --xram-loc 0x0000 --xram-size 0x400 4 | ASFLAGS = -plosgff 5 | RELFILES = main.rel spicmd.rel gfx.rel osd.rel config.rel 6 | 7 | $(TARGET): gfx_osd.h $(RELFILES) 8 | sdcc $(CFLAGS) $(LFLAGS) $(RELFILES) -o $(TARGET) 9 | 10 | %.rel: %.c 11 | sdcc $(CFLAGS) -c $< 12 | 13 | %.rel: %.asm 14 | sdas8051 $(ASFLAGS) $@ $< 15 | 16 | gfx_osd.h: 17 | ./imgconv.py images gfx_osd.h 18 | 19 | clean: 20 | rm --force *.ihx *.lnk *.lst *.map *.rel *.rst *.sym *.mem *.asm *.lk flash.cdb flash.omf gfx_osd.h 21 | 22 | all: clean main 23 | 24 | -------------------------------------------------------------------------------- /config.h: -------------------------------------------------------------------------------- 1 | 2 | #define CFG_BACKLIGHT_LEVELS sizeof(gamma_table) 3 | #define CFG_PALETTE_COUNT 16 4 | #define CFG_MAX_POS_H 80 5 | #define CFG_MAX_POS_V 72 6 | 7 | #define CONFIG_MAGIC 0x2AB439C7 // increment with 'config_data_t' changes 8 | 9 | typedef struct 10 | { 11 | uint16_t color[4]; 12 | } cfg_palette_t; 13 | 14 | typedef struct 15 | { 16 | uint32_t magic; 17 | uint8_t brightness; 18 | uint8_t palette; 19 | uint8_t grid; 20 | uint8_t battery; 21 | uint8_t pos_h; 22 | uint8_t pos_v; 23 | cfg_palette_t pal[CFG_PALETTE_COUNT]; 24 | } config_data_t; 25 | 26 | // 27 | 28 | extern __xdata config_data_t config; 29 | 30 | #ifdef ADC_BACKLIGHT 31 | extern __code const uint8_t gamma_table[16]; 32 | #else 33 | extern __code const uint8_t gamma_table[11]; 34 | #endif 35 | 36 | // 37 | 38 | void config_init(); 39 | void config_save(); 40 | void config_apply_palette(uint8_t idx); 41 | -------------------------------------------------------------------------------- /gfx.h: -------------------------------------------------------------------------------- 1 | 2 | #define GFX_ICON_WIDTH 35 // last column is (kinda) ignored 3 | #define GFX_ICON_HEIGHT 32 4 | #define GFX_ICON_STRIDE 18 // 36 pixels 5 | 6 | #define GFX_TEXT_HEIGHT 10 7 | 8 | // 9 | enum 10 | { 11 | // main icons 12 | GICON_BRIGHTNESS, 13 | GICON_PALETTE, 14 | GICON_COLORS, 15 | GICON_GRID, 16 | GICON_BATTERY, 17 | GICON_HPOS, 18 | GICON_VPOS, 19 | // main strings 20 | GTEXT_BRIGHTNESS, 21 | GTEXT_PALETTE, 22 | GTEXT_COLORS, 23 | GTEXT_GRID, 24 | GTEXT_BATTERY, 25 | GTEXT_HPOS, 26 | GTEXT_VPOS, 27 | // numbers 28 | GNUM_0, 29 | GNUM_1, 30 | GNUM_2, 31 | GNUM_3, 32 | GNUM_4, 33 | GNUM_5, 34 | GNUM_6, 35 | GNUM_7, 36 | GNUM_8, 37 | GNUM_9, 38 | GNUM_A, 39 | GNUM_B, 40 | GNUM_C, 41 | GNUM_D, 42 | GNUM_E, 43 | GNUM_F, 44 | // 45 | NUM_GFX_ICONS 46 | }; 47 | 48 | // 49 | 50 | extern const __code uint8_t logo_pal_idx[2]; 51 | extern const __code uint8_t osd_pal_idx[4]; 52 | 53 | // 54 | 55 | uint8_t gfx_read_2bpp(); 56 | 57 | void gfx_show_logo(uint8_t y); 58 | void gfx_show_icon(int16_t x, int8_t y, uint8_t idx); 59 | 60 | -------------------------------------------------------------------------------- /spicmd.h: -------------------------------------------------------------------------------- 1 | 2 | typedef struct 3 | { 4 | uint8_t cmd; 5 | union 6 | { 7 | struct 8 | { 9 | uint8_t value; 10 | } simple; 11 | struct 12 | { 13 | uint16_t color[4]; 14 | } palette; 15 | struct 16 | { 17 | uint16_t address; 18 | // uint8_t data[80]; 19 | } osd; 20 | }; 21 | } spi_cmd_t; 22 | 23 | // 24 | 25 | extern __xdata spi_cmd_t spicmd; 26 | 27 | extern __xdata uint8_t spicmd_osd_line_offs; 28 | extern __xdata uint8_t spicmd_osd_line_len; 29 | 30 | // 31 | void spi_send(uint8_t len); 32 | void spicmd_osd_clear(uint8_t color); 33 | void spicmd_osd_line(uint8_t y, uint8_t color); 34 | 35 | void spicmd_xbm_line(const __code uint8_t *); 36 | void spicmd_img_line(); 37 | 38 | // macros 39 | 40 | #define spicmd_backlight(val) do{spicmd.cmd = 0x01; spicmd.simple.value = val; spi_send(2);}while(0) 41 | #define spicmd_temp_num(val) do{spicmd.cmd = 0x02; spicmd.simple.value = val; spi_send(2);}while(0) 42 | #define spicmd_palette_full() do{spicmd.cmd = 0x03; spi_send(1+sizeof(uint16_t)*4);}while(0) 43 | #define spicmd_palette_0() do{spicmd.cmd = 0x04; spi_send(1+sizeof(uint16_t));}while(0) 44 | #define spicmd_palette_1() do{spicmd.cmd = 0x05; spi_send(1+sizeof(uint16_t));}while(0) 45 | #define spicmd_palette_2() do{spicmd.cmd = 0x06; spi_send(1+sizeof(uint16_t));}while(0) 46 | #define spicmd_palette_3() do{spicmd.cmd = 0x07; spi_send(1+sizeof(uint16_t));}while(0) 47 | #define spicmd_palette_osd() do{spicmd.cmd = 0x08; spi_send(1+sizeof(uint16_t));}while(0) 48 | #define spicmd_v_position(val) do{spicmd.cmd = 0x09; spicmd.simple.value = val; spi_send(2);}while(0) 49 | #define spicmd_h_position(val) do{spicmd.cmd = 0x0A; spicmd.simple.value = val; spi_send(2);}while(0) 50 | #define spicmd_show_grid(val) do{spicmd.cmd = 0x0B; spicmd.simple.value = val; spi_send(2);}while(0) 51 | #define spicmd_show_battery(val) do{spicmd.cmd = 0x0C; spicmd.simple.value = val; spi_send(2);}while(0) 52 | // 0x0D: OSD data 53 | 54 | -------------------------------------------------------------------------------- /defs.h: -------------------------------------------------------------------------------- 1 | 2 | #define RGB16(r,g,b) (((uint16_t)(r & 0xF8) << 8) | ((uint16_t)(g & 0xFC) << 3) | (uint16_t)(b >> 3)) 3 | #define RGBHTML(color) ((uint16_t)((color & 0xF80000) >> 8) | (uint16_t)((color & 0xFC00) >> 5) | (uint16_t)((color & 0xF8) >> 3)) 4 | 5 | #define ADC_MEAS_DELAY 10 // this alternates between 'battery' and 'backlight' 6 | 7 | #define OUT_CS P12 8 | #define OUT_OSD P16 9 | 10 | #define OSD_WIDTH 160 11 | #define OSD_HEIGHT 52 12 | 13 | #define OSD_STRIDE (OSD_WIDTH / 2) 14 | 15 | #define OSD_CENTER (OSD_WIDTH / 2) 16 | 17 | #define OSD_ICON_GAP 70 18 | #define OSD_DIGIT_GAP 18 19 | 20 | #define OSD_MAIN_ICON_Y 2 21 | #define OSD_MAIN_TEXT_Y (OSD_HEIGHT - 13) 22 | 23 | #define OSD_SIDE_ICON_Y (OSD_HEIGHT - 36) 24 | #define OSD_SIDE_TEXT_Y 1 25 | 26 | // 27 | 28 | extern __xdata uint16_t tick_ms; 29 | extern __xdata uint8_t tick_tmp; 30 | extern __xdata void (*ticker)(); 31 | 32 | // 33 | // input 34 | 35 | #define IN_LEFT P36 36 | #define IN_RIGHT P33 37 | #define IN_ENTER P32 38 | #define IN_TOUCH_L P17 39 | #define IN_TOUCH_R P37 40 | 41 | // 42 | // customization 43 | 44 | // hide cartridge logo 45 | //#define CFG_HIDE_CART_LOGO 46 | 47 | // show custom logo 48 | #define CFG_SHOW_CUSTOM_LOGO 49 | 50 | // custom logo start location 51 | // when > OSD_HEIGHT it also acts as delay 52 | #define CFG_LOGO_START_Y 90 53 | 54 | // custom logo scroll speed 55 | // only use (2^x)-1 56 | #define CFG_LOGO_TICK_MASK 0x1F 57 | 58 | // custom logo active color (from OSD palette) 59 | #define CFG_LOGO_COLOR 4 60 | 61 | // OSD colors 62 | #define OSD_COLOR_BACKGROUND 4 63 | #define OSD_COLOR_TOP 1 64 | #define OSD_COLOR_BOT 1 65 | 66 | // low battery raw ADC value 67 | #define ADC_LOW_BAT 0x1A00 // 0x1A00 = ~2.3V 68 | 69 | // use ADC for backlight level 70 | //#define ADC_BACKLIGHT 12 71 | #define ADC_BL_RAW_LO 0x6500 72 | #define ADC_BL_RAW_HI 0xDB00 73 | 74 | // OSD address offset 75 | // use if your image appears shifted 76 | // NOTE: offset is in bytes and not pixels 77 | //#define SPI_OSDADDR_OFFSET -2 78 | 79 | -------------------------------------------------------------------------------- /imgconv.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import sys 3 | from os import listdir 4 | from os import path 5 | from PIL import Image 6 | 7 | def get_files(dirname): 8 | filelist = [f for f in listdir(dirname) if path.isfile(path.join(dirname, f)) and path.splitext(f)[1] == ".png"] 9 | return filelist 10 | 11 | def dump_buffer(f, data, name): 12 | f.write("static __code const uint8_t %s[] =\n{\n\t" % name) 13 | for b in data: 14 | f.write("0x%02X," % b) 15 | f.write("\n};\n\n") 16 | 17 | # check arguments 18 | if len(sys.argv) != 3: 19 | print("usage:", sys.argv[0], "input_dir output") 20 | exit(1) 21 | 22 | # get image list 23 | files = get_files(sys.argv[1]) 24 | if len(files) <= 0: 25 | print("No images found!") 26 | exit(1) 27 | 28 | # open output file 29 | f = open(sys.argv[2], "w") 30 | f.write("// GENERATED FILE; DO NOT MODIFY\n// YOUR CHANGES WILL BE LOST!\n\n") 31 | 32 | # parse images 33 | for name in files: 34 | file = path.join(sys.argv[1], name) 35 | if name == "logo.png": 36 | img = Image.open(file).convert("L") 37 | if img.width != 160 or img.height != 52: 38 | raise Exception("Invalid logo resolution!") 39 | pix = img.getdata() 40 | data = bytearray() 41 | for i in range(0, 160 * 52, 8): 42 | color = pix[i] >> 7 43 | color |= (pix[i+1] >> 6) & 0x02 44 | color |= (pix[i+2] >> 5) & 0x04 45 | color |= (pix[i+3] >> 4) & 0x08 46 | color |= (pix[i+4] >> 3) & 0x10 47 | color |= (pix[i+5] >> 2) & 0x20 48 | color |= (pix[i+6] >> 1) & 0x40 49 | color |= pix[i+7] & 0x80 50 | data.append(color) 51 | dump_buffer(f, data, path.splitext(name)[0]) 52 | else: 53 | pix = Image.open(file).convert("L") 54 | width = pix.width + 4 + 3 # 4 for edges, 3 for padding 55 | width &= 0xFC 56 | height = pix.height + 2 # 2 for edges 57 | img = Image.new("L", (width, height)) 58 | img.paste(pix, ((width - pix.width) // 2, 1)) 59 | pix = img.getdata() 60 | data = bytearray() 61 | size = width * height 62 | data.append(width) 63 | data.append(height) 64 | for i in range(0, size, 4): 65 | color = pix[i] >> 6 66 | color |= (pix[i+1] & 0xC0) >> 4 67 | color |= (pix[i+2] & 0xC0) >> 2 68 | color |= pix[i+3] & 0xC0 69 | data.append(color) 70 | dump_buffer(f, data, path.splitext(name)[0]) 71 | 72 | # done 73 | f.close() 74 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # IPS kit OSD replacement 2 | 3 | This is open source alternative of STC8G1K17 MCU which controls OSD in this **specific** IPS LCD kit for **gameboy pocket**. 4 | 5 | ## Warning 6 | 7 | Pinout, SPI commands and stuff is based on **reverse engineering** of this specific IPS LCD kit! 8 | 9 | If you decide to use this firmware i assume you are willing to take **risk of bricking** your IPS LCD kit! 10 | 11 | **Currently, there is no way to return to the original firmware!** 12 | 13 | **Pay close attention to the PCB board photo i provided!** 14 | 15 | ## Options 16 | 17 | This firmware provides multiple compile-time options. These can be changed in `defs.h`. 18 | 19 | ## Requirements 20 | 21 | - Python 3 22 | - with `pillow` library 23 | - Make 24 | - SDCC 25 | - [stcgal](https://github.com/grigorig/stcgal/) (flashing tool) 26 | 27 | ## How to compile 28 | 29 | - install requirements 30 | - enter project directory 31 | - use `make` command 32 | 33 | ## How to flash 34 | 35 | `stcgal.py -o program_eeprom_split=16384 flash.ihx` 36 | 37 | It is important to set `program_eeprom_split`! 38 | 39 | Connect `Tx -> Rx` and `Rx -> Tx`. Use only 3V3 UART adapter! 40 | 41 | ## SPI byte transfer 42 | 43 | Timing between SPI bytes is forced by the code. It is possible that current values won't work for you. 44 | 45 | In such case just change values in each `spi_wait` call. Or add something like `ttt += 4` into `spi_wait`. 46 | 47 | Find out values that work for you but do not slow OSD too much. 48 | 49 | Function `spi_wait` is in file `spicmd.c`. 50 | 51 | ## OSD RAM offset 52 | 53 | There seems to be possible difference with different FPGAs. 54 | 55 | If you find that your image is shifted to the side a bit, use `SPI_OSDADDR_OFFSET` compile option. 56 | 57 | ## How to enter the menu 58 | 59 | Touch both touch inputs at the same time. 60 | 61 | ### How to leave the menu 62 | 63 | Go to main menu and touch both touch inputs at the same time. 64 | 65 | ## Input mapping 66 | 67 | Technically, it should be possible to rewire three available inputs (A, B and select) to different keys. 68 | 69 | ## Icons 70 | 71 | Graphics, including every text, is in `images` directory. These PNG files automaticaly are converted when building using make. 72 | 73 | Icons and texts can use 4 "shades". Palette is hardcoded due to OSD limitations. 74 | 75 | ## Fake boot logo 76 | 77 | If enabled, OSD is used to scroll a custom logo on power-on. 78 | 79 | Beware, `logo.png` should only use two colors! 80 | 81 | ## Use Brightness pot 82 | 83 | You can use original `contrast` pot to control `brightness` but it **requires** extra **hardware modification**. 84 | 85 | If you don't have this modification disable `ADC_BACKLIGHT` compile option! 86 | 87 | This modification is single wire and single capacitor, about 1uF. 88 | 89 | ## Images 90 | 91 | ![OLD OSD](doc/old_osd.png) 92 | 93 | ![main board](doc/the_board.jpg) 94 | 95 | ![FPC wire](doc/wire_fpc.jpg) 96 | 97 | -------------------------------------------------------------------------------- /reg.h: -------------------------------------------------------------------------------- 1 | 2 | #define SFR(x, y) __sfr __at(y) x 3 | #define SBIT(x, y, z) __sbit __at(y+z) x 4 | 5 | // GPIO modes: 6 | // 0) 8051 bidir 7 | // 1) push pull 8 | // 2) input 9 | // 3) open drain 10 | 11 | // 12 | // PORT 0 13 | SFR(P0M0, 0x94); 14 | SFR(P0M1, 0x93); 15 | SFR(P0, 0x80); 16 | SBIT(P00, 0x80, 0); 17 | SBIT(P01, 0x80, 1); 18 | SBIT(P02, 0x80, 2); 19 | SBIT(P03, 0x80, 3); 20 | SBIT(P04, 0x80, 4); 21 | SBIT(P05, 0x80, 5); 22 | SBIT(P06, 0x80, 6); 23 | SBIT(P07, 0x80, 7); 24 | 25 | // 26 | // PORT 1 27 | SFR(P1M0, 0x92); 28 | SFR(P1M1, 0x91); 29 | SFR(P1, 0x90); 30 | SBIT(P10, 0x90, 0); 31 | SBIT(P11, 0x90, 1); 32 | SBIT(P12, 0x90, 2); 33 | SBIT(P13, 0x90, 3); 34 | SBIT(P14, 0x90, 4); 35 | SBIT(P15, 0x90, 5); 36 | SBIT(P16, 0x90, 6); 37 | SBIT(P17, 0x90, 7); 38 | 39 | // 40 | // PORT 2 41 | SFR(P2M0, 0x96); 42 | SFR(P2M1, 0x95); 43 | SFR(P2, 0xA0); 44 | SBIT(P20, 0xA0, 0); 45 | SBIT(P21, 0xA0, 1); 46 | SBIT(P22, 0xA0, 2); 47 | SBIT(P23, 0xA0, 3); 48 | SBIT(P24, 0xA0, 4); 49 | SBIT(P25, 0xA0, 5); 50 | SBIT(P26, 0xA0, 6); 51 | SBIT(P27, 0xA0, 7); 52 | 53 | // 54 | // PORT 3 55 | SFR(P3M0, 0xB2); 56 | SFR(P3M1, 0xB1); 57 | SFR(P3, 0xB0); 58 | SBIT(P30, 0xB0, 0); 59 | SBIT(P31, 0xB0, 1); 60 | SBIT(P32, 0xB0, 2); 61 | SBIT(P33, 0xB0, 3); 62 | SBIT(P34, 0xB0, 4); 63 | SBIT(P35, 0xB0, 5); 64 | SBIT(P36, 0xB0, 6); 65 | SBIT(P37, 0xB0, 7); 66 | 67 | // SPI status 68 | // 6) WCOL 69 | // 7) SPIF 70 | 71 | // SPI config 72 | // 0:1) SPR; clock div: 4, 8, 16, 32 73 | // 2) CPHA 74 | // 3) CPOL 75 | // 4) MSTR 76 | // 5) DORD; 1 = LSB first 77 | // 6) SPEN 78 | // 7) SSIG; 1 = ignore SS 79 | 80 | // 81 | // SPI 82 | SFR(SPSTAT, 0xCD); 83 | SFR(SPCTL, 0xCE); 84 | SFR(SPDAT, 0xCF); 85 | #define IRQ_SPI 9 86 | 87 | // 88 | // IRQ 89 | SBIT(EX0, 0xA8, 0); 90 | SBIT(ET0, 0xA8, 1); 91 | SBIT(EX1, 0xA8, 2); 92 | SBIT(ET1, 0xA8, 3); 93 | SBIT(ES, 0xA8, 4); 94 | SBIT(EADC, 0xA8, 5); 95 | SBIT(ELVD, 0xA8, 6); 96 | SBIT(EA, 0xA8, 7); 97 | SFR(IE, 0xA8); 98 | SFR(IE2, 0xAF); 99 | 100 | // 101 | // power 102 | SFR(PCON, 0x87); 103 | 104 | // 105 | // Timer 0 and 1 106 | SFR(TCON, 0x88); 107 | SFR(TMOD, 0x89); 108 | 109 | // 110 | // Timer 0 111 | SFR(TL0, 0x8A); 112 | SFR(TH0, 0x8C); 113 | SBIT(TR0, 0x88, 4); 114 | SBIT(TF0, 0x88, 5); 115 | #define IRQ_TIMER0 1 116 | 117 | // 118 | // Timer 1 119 | SFR(TL1, 0x8B); 120 | SFR(TH1, 0x8D); 121 | SBIT(TR1, 0x88, 6); 122 | SBIT(TF1, 0x88, 7); 123 | #define IRQ_TIMER1 3 124 | 125 | // 126 | // AUX 127 | SFR(AUXR, 0x8E); 128 | 129 | // 130 | // ADC 131 | SFR(ADC_CONTR, 0xBC); 132 | SFR(ADC_RES, 0xBD); 133 | SFR(ADC_RESL, 0xBE); 134 | SFR(ADCCFG, 0xDE); 135 | #define ADCTIM *((__xdata uint8_t*)0xFEA8) 136 | #define IRQ_ADC 5 137 | 138 | // 139 | // IAP 140 | SFR(IAP_DATA, 0xC2); 141 | SFR(IAP_ADDRH, 0xC3); 142 | SFR(IAP_ADDRL, 0xC4); 143 | SFR(IAP_CMD, 0xC5); 144 | SFR(IAP_TRIG, 0xC6); 145 | SFR(IAP_CONTR, 0xC7); 146 | SFR(IAP_TPS, 0xF5); 147 | 148 | -------------------------------------------------------------------------------- /spicmd.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "defs.h" 5 | #include "reg.h" 6 | #include "spicmd.h" 7 | #include "gfx.h" 8 | 9 | // NOTE: 10 | // Timing between SPI bytes is forced by the code. 11 | // It is possible that current values won't work for you. 12 | // In such case just change values in each 'spi_wait' call. 13 | // (or add something like 'ttt += 4' into 'spi_wait') 14 | // Find out values that work for you but do not slow OSD too much. 15 | 16 | __xdata spi_cmd_t spicmd; 17 | 18 | // 19 | // waiting 20 | 21 | static void spi_wait(uint8_t ttt) 22 | { 23 | while(--ttt); 24 | } 25 | 26 | // 27 | // data transfer 28 | 29 | void spi_send(uint8_t len) 30 | { 31 | __xdata uint8_t *ptr = (__xdata uint8_t*)&spicmd; 32 | 33 | OUT_CS = 0; 34 | 35 | do 36 | { 37 | SPDAT = *ptr; 38 | ptr++; 39 | spi_wait(2); 40 | } while(--len); 41 | 42 | spi_wait(3); 43 | OUT_CS = 1; 44 | } 45 | 46 | // 47 | // API 48 | 49 | void spicmd_osd_clear(uint8_t color) 50 | { 51 | color |= color << 4; 52 | 53 | for(uint8_t y = 0; y < 65; y++) 54 | { 55 | uint8_t x = 64; 56 | 57 | OUT_CS = 0; 58 | 59 | SPDAT = 0x0D; 60 | spi_wait(4); 61 | SPDAT = y << 6; 62 | spi_wait(4); 63 | SPDAT = y >> 2; 64 | spi_wait(4); 65 | 66 | do 67 | { 68 | SPDAT = color; 69 | spi_wait(4); 70 | } while(--x); 71 | 72 | spi_wait(3); 73 | OUT_CS = 1; 74 | spi_wait(3); 75 | } 76 | } 77 | 78 | void spicmd_osd_line(uint8_t y, uint8_t color) 79 | { 80 | uint16_t addr; 81 | 82 | if(y >= OSD_HEIGHT) 83 | return; 84 | 85 | color |= color << 4; 86 | 87 | addr = (uint16_t)y * OSD_STRIDE; 88 | 89 | #ifdef SPI_OSDADDR_OFFSET 90 | addr += (uint16_t)SPI_OSDADDR_OFFSET; 91 | #endif 92 | 93 | OUT_CS = 0; 94 | 95 | SPDAT = 0x0D; 96 | spi_wait(4); 97 | SPDAT = addr & 0xFF; 98 | spi_wait(4); 99 | SPDAT = addr >> 8; 100 | spi_wait(5); 101 | 102 | y = OSD_STRIDE; 103 | do 104 | { 105 | SPDAT = color; 106 | spi_wait(4); 107 | } while(--y); 108 | 109 | spi_wait(3); 110 | OUT_CS = 1; 111 | } 112 | 113 | void spicmd_xbm_line(const __code uint8_t *data) 114 | { 115 | __xdata uint8_t *ptr = (__xdata uint8_t*)&spicmd; 116 | uint8_t len = 3; 117 | 118 | OUT_CS = 0; 119 | 120 | // command 121 | do 122 | { 123 | SPDAT = *ptr; 124 | ptr++; 125 | spi_wait(2); 126 | } while(--len); 127 | 128 | // data 129 | len = OSD_WIDTH / 8; 130 | do 131 | { 132 | register uint8_t bits = *data++; 133 | register uint8_t out; 134 | 135 | out = logo_pal_idx[bits & 1]; 136 | out |= logo_pal_idx[(bits >> 1) & 1] << 4; 137 | SPDAT = out; 138 | spi_wait(1); 139 | 140 | out = logo_pal_idx[(bits >> 2) & 1]; 141 | out |= logo_pal_idx[(bits >> 3) & 1] << 4; 142 | SPDAT = out; 143 | spi_wait(1); 144 | 145 | out = logo_pal_idx[(bits >> 4) & 1]; 146 | out |= logo_pal_idx[(bits >> 5) & 1] << 4; 147 | SPDAT = out; 148 | spi_wait(1); 149 | 150 | out = logo_pal_idx[(bits >> 6) & 1]; 151 | out |= logo_pal_idx[(bits >> 7) & 1] << 4; 152 | SPDAT = out; 153 | spi_wait(1); 154 | } while(--len); 155 | 156 | spi_wait(4); 157 | OUT_CS = 1; 158 | } 159 | 160 | void spicmd_img_line() 161 | { 162 | __xdata uint8_t *ptr = (__xdata uint8_t*)&spicmd; 163 | uint8_t len = 3; 164 | 165 | OUT_CS = 0; 166 | 167 | // command 168 | do 169 | { 170 | SPDAT = *ptr; 171 | ptr++; 172 | spi_wait(2); 173 | } while(--len); 174 | 175 | // data 176 | while(1) 177 | { 178 | uint8_t out; 179 | uint8_t idx; 180 | 181 | idx = gfx_read_2bpp(); 182 | if(idx & 0x80) 183 | break; 184 | 185 | out = osd_pal_idx[idx]; 186 | 187 | idx = gfx_read_2bpp(); 188 | if(idx & 0x80) 189 | out |= out << 4; 190 | else 191 | out |= osd_pal_idx[idx] << 4; 192 | 193 | SPDAT = out; 194 | // no delay here 195 | } 196 | 197 | spi_wait(3); 198 | OUT_CS = 1; 199 | } 200 | 201 | -------------------------------------------------------------------------------- /gfx.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "reg.h" 5 | #include "defs.h" 6 | #include "spicmd.h" 7 | #include "gfx.h" 8 | 9 | // images + logo 10 | #include "gfx_osd.h" 11 | 12 | #ifdef OSD_COLOR_TOP 13 | #define CLIP_TOP 1 14 | #else 15 | #define CLIP_TOP 0 16 | #endif 17 | 18 | #ifdef OSD_COLOR_BOT 19 | #define CLIP_BOT (OSD_HEIGHT - 1) 20 | #else 21 | #define CLIP_BOT OSD_HEIGHT 22 | #endif 23 | 24 | // 25 | 26 | const __code uint8_t logo_pal_idx[2] = {15, CFG_LOGO_COLOR}; 27 | const __code uint8_t osd_pal_idx[4] = {4, 14, 15, 3}; 28 | 29 | // these are not in XRAM 30 | static const __code uint8_t *img_ptr; 31 | static uint8_t img_left; 32 | static uint8_t img_byte; 33 | static uint8_t img_bits; 34 | 35 | // icon list 36 | static __code const uint8_t *__code const gfx_icon_ptr[NUM_GFX_ICONS] = 37 | { 38 | [GICON_BRIGHTNESS] = i_brightness, 39 | [GICON_PALETTE] = i_palette, 40 | [GICON_COLORS] = i_colors, 41 | [GICON_GRID] = i_grid, 42 | [GICON_BATTERY] = i_battery, 43 | [GICON_HPOS] = i_hpos, 44 | [GICON_VPOS] = i_vpos, 45 | 46 | [GTEXT_BRIGHTNESS] = t_brightness, 47 | [GTEXT_PALETTE] = t_palette, 48 | [GTEXT_COLORS] = t_colors, 49 | [GTEXT_GRID] = t_grid, 50 | [GTEXT_BATTERY] = t_battery, 51 | [GTEXT_HPOS] = t_hpos, 52 | [GTEXT_VPOS] = t_vpos, 53 | 54 | [GNUM_0] = i_num0, 55 | [GNUM_1] = i_num1, 56 | [GNUM_2] = i_num2, 57 | [GNUM_3] = i_num3, 58 | [GNUM_4] = i_num4, 59 | [GNUM_5] = i_num5, 60 | [GNUM_6] = i_num6, 61 | [GNUM_7] = i_num7, 62 | [GNUM_8] = i_num8, 63 | [GNUM_9] = i_num9, 64 | [GNUM_A] = i_numA, 65 | [GNUM_B] = i_numB, 66 | [GNUM_C] = i_numC, 67 | [GNUM_D] = i_numD, 68 | [GNUM_E] = i_numE, 69 | [GNUM_F] = i_numF, 70 | }; 71 | 72 | // 73 | // API 74 | 75 | void gfx_show_logo(uint8_t y) 76 | { 77 | const __code uint8_t *data = logo; 78 | 79 | spicmd.cmd = 0x0D; 80 | spicmd.osd.address = (uint16_t)y * OSD_STRIDE; 81 | 82 | #ifdef SPI_OSDADDR_OFFSET 83 | spicmd.osd.address += (uint16_t)SPI_OSDADDR_OFFSET; 84 | #endif 85 | 86 | for( ; y < OSD_HEIGHT; y++) 87 | { 88 | spicmd_xbm_line(data); 89 | spicmd.osd.address += OSD_STRIDE; 90 | data += OSD_WIDTH / 8; 91 | } 92 | } 93 | 94 | void gfx_show_icon(int16_t x, int8_t y, uint8_t idx) 95 | { 96 | const __code uint8_t *data; 97 | uint8_t width, stride, skip; 98 | int8_t ye; 99 | int16_t xe; 100 | 101 | if(y >= CLIP_BOT) 102 | return; 103 | 104 | data = gfx_icon_ptr[idx]; 105 | 106 | stride = *data++; 107 | 108 | x -= stride / 2; // autocenter 109 | 110 | if(x >= OSD_WIDTH - 1) // last column can't be used 111 | return; 112 | 113 | ye = *data++; 114 | ye += y; 115 | 116 | if(y < CLIP_TOP) 117 | { 118 | data += (CLIP_TOP - y) * stride / 4; 119 | y = CLIP_TOP; 120 | } 121 | 122 | if(ye > CLIP_BOT) 123 | ye = CLIP_BOT; 124 | 125 | xe = x + stride; 126 | 127 | if(xe <= 0) 128 | return; 129 | 130 | if(xe > OSD_WIDTH) 131 | xe = OSD_WIDTH; 132 | 133 | width = xe - x; 134 | 135 | spicmd.cmd = 0x0D; 136 | spicmd.osd.address = (uint16_t)y * OSD_STRIDE; 137 | 138 | if(x < 0) 139 | { 140 | skip = -x & 3; 141 | x /= 4; 142 | data -= x; 143 | width += x * 4; 144 | } else 145 | { 146 | skip = x & 1; 147 | spicmd.osd.address += (x + 1) / 2; 148 | } 149 | 150 | stride /= 4; 151 | 152 | #ifdef SPI_OSDADDR_OFFSET 153 | spicmd.osd.address += (uint16_t)SPI_OSDADDR_OFFSET; 154 | #endif 155 | 156 | for( ; y < ye; y++) 157 | { 158 | img_ptr = data; 159 | img_left = width; 160 | img_bits = 0; 161 | 162 | data += stride; 163 | 164 | for(uint8_t tmp = 0; tmp < skip; tmp++) 165 | gfx_read_2bpp(); 166 | 167 | spicmd_img_line(); 168 | 169 | spicmd.osd.address += OSD_STRIDE; 170 | } 171 | } 172 | 173 | uint8_t gfx_read_2bpp() 174 | { 175 | uint8_t ret; 176 | 177 | if(!img_left) 178 | return 0xFF; 179 | 180 | if(!img_bits) 181 | { 182 | img_byte = *img_ptr; 183 | img_ptr++; 184 | img_bits = 4; 185 | } 186 | 187 | ret = img_byte & 3; 188 | img_byte >>= 2; 189 | 190 | img_bits--; 191 | img_left--; 192 | 193 | return ret; 194 | } 195 | 196 | -------------------------------------------------------------------------------- /config.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "reg.h" 5 | #include "defs.h" 6 | #include "spicmd.h" 7 | #include "config.h" 8 | 9 | // DO NOT FORGET: 10 | // program_eeprom_split=16384 11 | 12 | #define TPS_VALUE 24 13 | 14 | __xdata config_data_t config; 15 | 16 | // PWM gamma table 17 | #ifdef ADC_BACKLIGHT 18 | __code const uint8_t gamma_table[16] = 19 | { 20 | 0, 1, 2, 3, 4, 5, 6, 8, 10, 12, 15, 18, 21, 24, 28, 32 21 | }; 22 | #else 23 | __code const uint8_t gamma_table[11] = 24 | { 25 | 0, 1, 2, 3, 4, 6, 9, 13, 18, 25, 32 26 | }; 27 | #endif 28 | 29 | // stored config 30 | static __code __at(0x4000) const config_data_t config_flash; 31 | 32 | // default config 33 | static __code const config_data_t config_default = 34 | { 35 | .magic = CONFIG_MAGIC, 36 | .brightness = 0, 37 | .palette = 0, 38 | .grid = 1, 39 | .battery = 1, 40 | .pos_h = 40, 41 | .pos_v = 30, 42 | .pal = 43 | { 44 | {{ // white to black 45 | RGB16(255, 255, 255), 46 | RGB16(170, 170, 170), 47 | RGB16(85, 85, 85), 48 | RGB16(0, 0, 0), 49 | }}, 50 | {{ // MGB 51 | RGBHTML(0xF1FAE7), 52 | RGBHTML(0x93A384), 53 | RGBHTML(0x55624C), 54 | RGBHTML(0x151E0F), 55 | }}, 56 | {{ // MGB (alt) 57 | RGBHTML(0xC0FF98), 58 | RGBHTML(0x30B0A8), 59 | RGBHTML(0x187688), 60 | RGBHTML(0x003428), 61 | }}, 62 | {{ // DMG 63 | RGBHTML(0xB5FF52), 64 | RGBHTML(0x52B24D), 65 | RGBHTML(0x007018), 66 | RGBHTML(0x002C00), 67 | }}, 68 | {{ // DMG bivert (original - blue) 69 | RGBHTML(0xCCE7F1), 70 | RGBHTML(0x7E959E), 71 | RGBHTML(0x5F699A), 72 | RGBHTML(0x001090), 73 | }}, 74 | {{ // DMG bivert (green) 75 | RGBHTML(0xCCF1E7), 76 | RGBHTML(0x7E9E95), 77 | RGBHTML(0x30703D), 78 | RGBHTML(0x004800), 79 | }}, 80 | {{ // DMG bivert (red) 81 | RGBHTML(0xF1E7CC), 82 | RGBHTML(0x9E957E), 83 | RGBHTML(0x805248), 84 | RGBHTML(0x600000), 85 | }}, 86 | {{ // emulator 87 | RGB16(252, 252, 124), 88 | RGB16(188, 188, 92), 89 | RGB16(124, 124, 60), 90 | RGB16(60, 60, 28), 91 | }}, 92 | {{ // red to black 93 | RGB16(255, 0, 0), 94 | RGB16(170, 0, 0), 95 | RGB16(85, 0, 0), 96 | RGB16(0, 0, 0), 97 | }}, 98 | {{ // green to black 99 | RGB16(0, 255, 0), 100 | RGB16(0, 170, 0), 101 | RGB16(0, 85, 0), 102 | RGB16(0, 0, 0), 103 | }}, 104 | {{ // blue to black 105 | RGB16(0, 0, 255), 106 | RGB16(0, 0, 170), 107 | RGB16(0, 0, 85), 108 | RGB16(0, 0, 0), 109 | }}, 110 | {{ // cyan to black 111 | RGB16(0, 255, 255), 112 | RGB16(0, 170, 170), 113 | RGB16(0, 85, 85), 114 | RGB16(0, 0, 0), 115 | }}, 116 | {{ // magenta to black 117 | RGB16(255, 0, 255), 118 | RGB16(170, 0, 170), 119 | RGB16(85, 0, 85), 120 | RGB16(0, 0, 0), 121 | }}, 122 | {{ // yellow to black 123 | RGB16(255, 255, 0), 124 | RGB16(170, 170, 0), 125 | RGB16(85, 85, 0), 126 | RGB16(0, 0, 0), 127 | }}, 128 | {{ // orange to black 129 | RGB16(255, 128, 0), 130 | RGB16(170, 85, 0), 131 | RGB16(85, 42, 0), 132 | RGB16(0, 0, 0), 133 | }}, 134 | {{ // violet to black 135 | RGB16(128, 0, 255), 136 | RGB16(85, 0, 170), 137 | RGB16(42, 0, 85), 138 | RGB16(0, 0, 0), 139 | }}, 140 | } 141 | }; 142 | 143 | // 144 | // stuff 145 | 146 | static void copy_config(__code const uint8_t *src) 147 | { 148 | __xdata uint8_t *dst = (__xdata uint8_t*)&config; 149 | uint8_t count = sizeof(config_data_t); 150 | do 151 | { 152 | *dst++ = *src++; 153 | } while(--count); 154 | } 155 | 156 | static void erase_config() 157 | { 158 | IAP_CONTR = 0x80; 159 | IAP_TPS = TPS_VALUE; 160 | IAP_CMD = 3; 161 | IAP_ADDRL = 0; 162 | IAP_ADDRH = 0; 163 | IAP_TRIG = 0x5A; 164 | IAP_TRIG = 0xA5; 165 | __asm 166 | nop 167 | __endasm; 168 | IAP_CONTR = 0x00; 169 | IAP_ADDRH = 0x80; 170 | IAP_CMD = 0; 171 | } 172 | 173 | static void write_config() 174 | { 175 | uint16_t dst = 0; 176 | __xdata const uint8_t *src = (__xdata const uint8_t*)&config; 177 | uint8_t count = sizeof(config_data_t); 178 | 179 | IAP_CONTR = 0x80; 180 | IAP_TPS = TPS_VALUE; 181 | IAP_CMD = 2; 182 | 183 | do 184 | { 185 | IAP_ADDRH = dst >> 8; 186 | IAP_ADDRL = dst & 0xFF; 187 | dst++; 188 | IAP_DATA = *src++; 189 | IAP_TRIG = 0x5A; 190 | IAP_TRIG = 0xA5; 191 | __asm 192 | nop 193 | __endasm; 194 | } while(--count); 195 | 196 | IAP_CONTR = 0x00; 197 | IAP_ADDRH = 0x80; 198 | IAP_CMD = 0; 199 | } 200 | 201 | // 202 | // API 203 | 204 | void config_init() 205 | { 206 | // check built-in config 207 | if(config_flash.magic == CONFIG_MAGIC) 208 | copy_config((__code const uint8_t*)&config_flash); 209 | else 210 | copy_config((__code const uint8_t*)&config_default); 211 | 212 | #ifdef ADC_BACKLIGHT 213 | spicmd_backlight(gamma_table[0]); 214 | #else 215 | spicmd_backlight(gamma_table[config.brightness]); 216 | #endif 217 | spicmd_show_grid(config.grid); 218 | spicmd_h_position(config.pos_h); 219 | spicmd_v_position(config.pos_v); 220 | } 221 | 222 | void config_save() 223 | { 224 | __code const uint8_t *src = (__code const uint8_t*)&config_flash; 225 | __xdata uint8_t *dst = (__xdata uint8_t*)&config; 226 | uint8_t count = sizeof(config_data_t); 227 | 228 | do 229 | { 230 | if(*dst != *src) 231 | { 232 | erase_config(); 233 | write_config(); 234 | break; 235 | } 236 | dst++; 237 | src++; 238 | } while(--count); 239 | 240 | } 241 | 242 | void config_apply_palette(uint8_t idx) 243 | { 244 | __xdata uint8_t *dst = (__xdata uint8_t*)spicmd.palette.color; 245 | __xdata uint8_t *src = (__xdata uint8_t*)config.pal[idx]; 246 | uint8_t count = 4 * sizeof(uint16_t); 247 | 248 | do 249 | { 250 | *dst++ = *src++; 251 | } while(--count); 252 | 253 | spicmd_palette_full(); 254 | } 255 | 256 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "reg.h" 5 | #include "defs.h" 6 | #include "spicmd.h" 7 | #include "gfx.h" 8 | #include "osd.h" 9 | #include "config.h" 10 | 11 | // timing is not exact due to RC oscillator and rounding 12 | // timer clock: 23.520 MHz / 12 13 | #define TIMER_MS 63576 14 | 15 | __xdata uint16_t tick_ms; 16 | __xdata uint8_t tick_tmp; 17 | __xdata void (*ticker)(); 18 | 19 | static uint8_t adc_tick = ADC_MEAS_DELAY - 1; 20 | 21 | #ifdef ADC_BACKLIGHT 22 | static __xdata uint16_t adc_val[2] = {0xFFFF, 0}; // battery starts as 'charged', brightness starts at 'lowest' 23 | static __xdata uint8_t adc_idx; 24 | static __xdata uint16_t adc_old; 25 | static __xdata uint8_t pwm_now = 0xFF; 26 | static __xdata uint8_t pwm_old = 0xFF; 27 | #else 28 | static __xdata uint16_t adc_val[1] = {0xFFFF}; 29 | #endif 30 | 31 | static __xdata uint8_t adc_lowbat; 32 | 33 | // 34 | // IRQ 35 | 36 | void timer0_irq() __naked __interrupt(IRQ_TIMER0) 37 | { 38 | // This function is set 'naked'! 39 | // That means this can be the only active code! 40 | 41 | if(adc_tick >= 2) 42 | { 43 | adc_tick--; 44 | if(adc_tick == 1) 45 | { 46 | // enable ADC 47 | uint8_t tmp = 0b10000000; 48 | #ifdef ADC_BACKLIGHT 49 | if(adc_idx) 50 | tmp |= ADC_BACKLIGHT; 51 | #endif 52 | ADC_CONTR = tmp; 53 | } 54 | } else 55 | if(adc_tick == 1) 56 | { 57 | // start ADC measure 58 | uint8_t tmp = 0b11000000; 59 | #ifdef ADC_BACKLIGHT 60 | if(adc_idx) 61 | tmp |= ADC_BACKLIGHT; 62 | #endif 63 | ADC_CONTR = tmp; 64 | adc_tick = 0; 65 | } else 66 | if(ADC_CONTR & 0b00100000) 67 | { 68 | #ifdef ADC_BACKLIGHT 69 | __xdata uint16_t *ptr = adc_val + adc_idx; 70 | #else 71 | __xdata uint16_t *ptr = adc_val; 72 | #endif 73 | uint16_t value; 74 | 75 | value = (uint16_t)ADC_RES << 8; 76 | value |= ADC_RESL; 77 | 78 | *ptr -= *ptr / 4; 79 | *ptr += value / 4; 80 | value = *ptr; 81 | #ifdef ADC_BACKLIGHT 82 | if(adc_idx) 83 | { 84 | uint16_t diff; 85 | 86 | if(adc_old < value) 87 | diff = value - adc_old; 88 | else 89 | diff = adc_old - value; 90 | 91 | if(diff >= 0x0100) 92 | { 93 | adc_old = value; 94 | 95 | if(value <= ADC_BL_RAW_LO) 96 | pwm_now = gamma_table[0]; 97 | else 98 | if(value >= ADC_BL_RAW_HI) 99 | pwm_now = gamma_table[sizeof(gamma_table) - 1]; 100 | else 101 | { 102 | value = ((value - ADC_BL_RAW_LO) >> 8) * (sizeof(gamma_table) - 2); 103 | value /= (uint8_t)((ADC_BL_RAW_HI - ADC_BL_RAW_LO) >> 8); 104 | pwm_now = gamma_table[1 + value]; 105 | } 106 | } 107 | } else 108 | #endif 109 | { 110 | if(value <= ADC_LOW_BAT) 111 | // this flag is never unset 112 | adc_lowbat = 1; 113 | } 114 | 115 | ADC_CONTR = 0; 116 | adc_tick = ADC_MEAS_DELAY; 117 | #ifdef ADC_BACKLIGHT 118 | adc_idx = !adc_idx; 119 | #endif 120 | } 121 | 122 | #ifdef ADC_BACKLIGHT 123 | if(pwm_now != pwm_old) 124 | { 125 | pwm_old = pwm_now; 126 | spicmd_backlight(pwm_now); 127 | } 128 | #endif 129 | 130 | if(adc_lowbat && config.battery) 131 | { 132 | if(!(tick_ms & 0x1FF)) 133 | spicmd_show_battery(!(tick_ms & 0x200)); 134 | } 135 | 136 | // tick 137 | tick_ms++; 138 | ticker(); 139 | 140 | // return opcode due to 'naked' function 141 | __asm 142 | reti 143 | __endasm; 144 | } 145 | 146 | // 147 | // logo scroll 148 | 149 | static void tick_logo_wait() 150 | { 151 | if(tick_ms < 5100) 152 | return; 153 | 154 | // set correct palette 155 | config_apply_palette(config.palette); 156 | 157 | // init OSD 158 | osd_init(); 159 | } 160 | 161 | static void tick_logo_up() 162 | { 163 | // scroll custom logo up 164 | 165 | if(tick_ms & CFG_LOGO_TICK_MASK) 166 | // this is logo scroll speed 167 | return; 168 | 169 | tick_tmp--; 170 | gfx_show_logo(tick_tmp); 171 | 172 | if(tick_tmp) 173 | return; 174 | 175 | ticker = tick_logo_wait; 176 | } 177 | 178 | static inline void setup_logo() 179 | { 180 | uint16_t *pc; 181 | 182 | #ifdef CFG_SHOW_CUSTOM_LOGO 183 | ticker = tick_logo_up; 184 | tick_tmp = CFG_LOGO_START_Y; 185 | #else 186 | ticker = tick_logo_wait; 187 | #endif 188 | 189 | // FPGA shows number '1' in top left corner every boot 190 | // hide this by faking palette color 191 | pc = config.pal[config.palette].color; 192 | 193 | spicmd.palette.color[0] = pc[0]; 194 | spicmd.palette.color[1] = pc[0]; 195 | spicmd.palette.color[2] = pc[0]; 196 | #ifdef CFG_HIDE_CART_LOGO 197 | spicmd.palette.color[3] = pc[0]; 198 | #else 199 | spicmd.palette.color[3] = pc[3]; 200 | #endif 201 | spicmd_palette_full(); 202 | 203 | // set OSD color to logo background color 204 | spicmd_palette_osd(); 205 | 206 | // clear OSD with custom color 207 | spicmd_osd_clear(0xFF); 208 | 209 | #ifdef CFG_SHOW_CUSTOM_LOGO 210 | // show OSD 211 | OUT_OSD = 0; 212 | #endif 213 | 214 | // start timer 215 | TR0 = 1; 216 | } 217 | 218 | // 219 | // setup 220 | 221 | static inline void init_io() 222 | { 223 | P0M0 = 0b00000000; 224 | P0M1 = 0b11111111; 225 | 226 | // !IN_COLOR; !OSD; CLK; MISO; MOSI; !CS; PWR?; ADC_BAT 227 | P1M0 = 0b01111110; 228 | P1M1 = 0b10000001; 229 | 230 | P2M0 = 0b00000000; 231 | P2M1 = 0b11111111; 232 | 233 | // !IN_LIGHT; !IN_A; -; -; !IN_B; !IN_SELECT; -; - 234 | P3M0 = 0b00000000; 235 | P3M1 = 0b11111111; 236 | 237 | // some kind of power supply enable? 238 | P11 = 0; 239 | 240 | // hide OSD 241 | OUT_OSD = 1; 242 | 243 | // SPI 244 | OUT_CS = 1; 245 | SPCTL = 0b11110000; 246 | 247 | // ADC setup 248 | ADCTIM = 0x3F; 249 | ADCCFG = 0b00001111; 250 | } 251 | 252 | static inline void init_system() 253 | { 254 | // setup logo scroll 255 | setup_logo(); 256 | 257 | // timer 258 | TMOD = 0b00000000; 259 | TL0 = TIMER_MS & 0xFF; 260 | TH0 = TIMER_MS >> 8; 261 | ET0 = 1; 262 | 263 | // enable IRQ 264 | EA = 1; 265 | } 266 | 267 | // 268 | // MAIN 269 | 270 | void main() 271 | { 272 | init_io(); 273 | config_init(); 274 | init_system(); 275 | 276 | while(1) 277 | { 278 | // enter IDLE 279 | PCON = 1; 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /gfx_logo.h: -------------------------------------------------------------------------------- 1 | // this is XBM image 2 | // always 160x52 3 | static __code const uint8_t logo_data[] = 4 | { 5 | 0x00, 0x00, 0x00, 0x00, 0x7c, 0xc0, 0x07, 0xf8, 0x01, 0xf0, 0x0f, 0x1e, 6 | 0x00, 0x0f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 7 | 0x7c, 0xe0, 0x03, 0xfe, 0x07, 0xfc, 0x3f, 0x1e, 0x00, 0x0f, 0xff, 0x0f, 8 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0xf0, 0x01, 0xff, 9 | 0x0f, 0xfe, 0x3f, 0x1e, 0x00, 0x8f, 0xff, 0x0f, 0x00, 0x00, 0x00, 0x00, 10 | 0x00, 0x00, 0x00, 0x00, 0x7c, 0xf8, 0x81, 0xff, 0x0f, 0xfe, 0x3f, 0x1e, 11 | 0x00, 0x8f, 0xff, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 12 | 0x7c, 0xf8, 0xc0, 0x1f, 0x0e, 0x3f, 0x38, 0x1c, 0x00, 0xc7, 0x0f, 0x0e, 13 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x7c, 0xc0, 0x0f, 14 | 0x08, 0x1f, 0x20, 0x3c, 0x00, 0xc7, 0x07, 0x08, 0x00, 0x00, 0x00, 0x00, 15 | 0x00, 0x00, 0x00, 0x00, 0x7c, 0x3e, 0xc0, 0x07, 0x00, 0x1f, 0x00, 0x3c, 16 | 0x9f, 0xc7, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 17 | 0x7c, 0x1f, 0xe0, 0x03, 0x00, 0x3f, 0x00, 0x3c, 0x9f, 0xc7, 0x0f, 0x00, 18 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x1f, 0xe0, 0x03, 19 | 0x00, 0xff, 0x01, 0x3c, 0x9f, 0xc7, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 20 | 0x00, 0x00, 0x00, 0x00, 0xfc, 0x0f, 0xe0, 0x03, 0x00, 0xfe, 0x07, 0x3c, 21 | 0x9f, 0x87, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 22 | 0xfc, 0x1f, 0xe0, 0xc3, 0x1f, 0xfc, 0x1f, 0x3c, 0x9f, 0x07, 0xff, 0x07, 23 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x1f, 0xe0, 0xc3, 24 | 0x1f, 0xf0, 0x3f, 0x3c, 0xbb, 0x07, 0xfc, 0x0f, 0x00, 0x00, 0x00, 0x00, 25 | 0x00, 0x00, 0x00, 0x00, 0xfc, 0x3f, 0xe0, 0xc3, 0x1f, 0xc0, 0x3f, 0xb8, 26 | 0xbb, 0x07, 0xf0, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 27 | 0xfc, 0x7e, 0xe0, 0xc3, 0x1f, 0x00, 0x7e, 0xb8, 0xbb, 0x03, 0x80, 0x1f, 28 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x7c, 0xe0, 0x03, 29 | 0x1e, 0x00, 0x7c, 0xb8, 0xbb, 0x03, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 30 | 0x00, 0x00, 0x00, 0x00, 0x7c, 0xf8, 0xe0, 0x07, 0x1e, 0x00, 0x7c, 0xf8, 31 | 0xbb, 0x03, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 32 | 0x7c, 0xf8, 0xc0, 0x07, 0x1e, 0x01, 0x7c, 0xf8, 0xf1, 0x43, 0x00, 0x1f, 33 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0xf0, 0xc1, 0x0f, 34 | 0x1e, 0x03, 0x7c, 0xf8, 0xf1, 0xc3, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 35 | 0x00, 0x00, 0x00, 0x00, 0x7c, 0xf0, 0xc1, 0x1f, 0x1e, 0x0f, 0x7e, 0xf8, 36 | 0xf1, 0xc3, 0x83, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 37 | 0x7c, 0xe0, 0x83, 0xff, 0x1f, 0xff, 0x3f, 0xf8, 0xf1, 0xc3, 0xff, 0x0f, 38 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0xe0, 0x03, 0xff, 39 | 0x1f, 0xff, 0x3f, 0xf0, 0xe1, 0xc3, 0xff, 0x0f, 0x00, 0x00, 0x00, 0x00, 40 | 0x00, 0x00, 0x00, 0x00, 0x7c, 0xc0, 0x07, 0xfe, 0x0f, 0xfe, 0x1f, 0xf0, 41 | 0xe0, 0x83, 0xff, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 42 | 0x7c, 0xc0, 0x07, 0xf8, 0x03, 0xf8, 0x07, 0xf0, 0xe0, 0x01, 0xfe, 0x01, 43 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 44 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 45 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 46 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 47 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 48 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 49 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 50 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 51 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 52 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 53 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 54 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 55 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 56 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 57 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 58 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 59 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 60 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 61 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 62 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 63 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 64 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 65 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 66 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 67 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 68 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 69 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 70 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 71 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 72 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 73 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 74 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 75 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 76 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 77 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 78 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 79 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 80 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 81 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 82 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 83 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 84 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 85 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 86 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 87 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 88 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 89 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 90 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 91 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 92 | }; 93 | -------------------------------------------------------------------------------- /osd.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "reg.h" 5 | #include "defs.h" 6 | #include "spicmd.h" 7 | #include "gfx.h" 8 | #include "config.h" 9 | #include "osd.h" 10 | 11 | // 12 | // NOTE: 13 | // Timing is only based on SPI transfer speeds. 14 | 15 | #define QUICK_SAVE_DELAY 2000 16 | 17 | #define MAIN_SELECT_SCROLL (OSD_HEIGHT - OSD_MAIN_ICON_Y) 18 | #define GAP_MAIN_ICON_TEXT (OSD_MAIN_TEXT_Y - OSD_MAIN_ICON_Y) 19 | #define LEN_SELECT_SCROLL (OSD_MAIN_TEXT_Y - OSD_SIDE_TEXT_Y) 20 | #define LEN_HEX_SCROLL (OSD_HEIGHT - OSD_SIDE_ICON_Y + HEX_NUMBER_OFFSET) 21 | #define SIDE_Y_START (OSD_SIDE_ICON_Y + 48) 22 | #define HEX_NUMBER_OFFSET 4 23 | 24 | #define COLOR_BUTTON_DELAY 100 25 | 26 | #ifdef ADC_BACKLIGHT 27 | #define MAIN_FIRST_ICON GICON_PALETTE 28 | #define MAIN_FIRST_TEXT GTEXT_PALETTE 29 | #else 30 | #define MAIN_FIRST_ICON GICON_BRIGHTNESS 31 | #define MAIN_FIRST_TEXT GTEXT_BRIGHTNESS 32 | #endif 33 | 34 | enum 35 | { 36 | #ifndef ADC_BACKLIGHT 37 | MENU_BRIGHTNESS, 38 | #endif 39 | MENU_PALETTE, 40 | MENU_COLORS, 41 | MENU_GRID, 42 | MENU_BATTERY, 43 | MENU_POS_H, 44 | MENU_POS_V, 45 | // 46 | MAIN_COUNT 47 | }; 48 | 49 | // 50 | 51 | static __xdata uint8_t menu_pos; 52 | static __xdata uint8_t menu_max; 53 | 54 | static __xdata uint8_t main_pos; 55 | static __xdata uint8_t color_pos; 56 | 57 | static __xdata uint8_t menu_icon_x; 58 | static __xdata int8_t menu_icon_y0; 59 | static __xdata int8_t menu_icon_y1; 60 | static __xdata uint8_t menu_icon_s; 61 | static __xdata uint8_t menu_icon_i; 62 | 63 | static __xdata uint16_t menu_save_wait; 64 | 65 | static __xdata uint8_t hexnum[6]; 66 | 67 | // menu item count 68 | static __code const uint8_t menu_side_limit[MAIN_COUNT] = 69 | { 70 | #ifndef ADC_BACKLIGHT 71 | [MENU_BRIGHTNESS] = CFG_BACKLIGHT_LEVELS, 72 | #endif 73 | [MENU_PALETTE] = CFG_PALETTE_COUNT, 74 | [MENU_COLORS] = 4, 75 | [MENU_GRID] = 2, 76 | [MENU_BATTERY] = 2, 77 | [MENU_POS_H] = CFG_MAX_POS_H, 78 | [MENU_POS_V] = CFG_MAX_POS_V, 79 | }; 80 | 81 | // config option pointer 82 | static __xdata uint8_t *__code const value_ptr[MAIN_COUNT] = 83 | { 84 | #ifndef ADC_BACKLIGHT 85 | [MENU_BRIGHTNESS] = &config.brightness, 86 | #endif 87 | [MENU_PALETTE] = &config.palette, 88 | [MENU_GRID] = &config.grid, 89 | [MENU_BATTERY] = &config.battery, 90 | [MENU_POS_H] = &config.pos_h, 91 | [MENU_POS_V] = &config.pos_v, 92 | }; 93 | 94 | // 95 | static void tick_main_menu(); 96 | static void tick_osd_idle(); 97 | 98 | // 99 | // stuff 100 | 101 | static void fake_palette() 102 | { 103 | uint16_t color = config.pal[config.palette].color[menu_pos]; 104 | 105 | spicmd.palette.color[0] = color; 106 | spicmd.palette.color[1] = color; 107 | spicmd.palette.color[2] = color; 108 | spicmd.palette.color[3] = color; 109 | spicmd_palette_full(); 110 | } 111 | 112 | static void split_palette() 113 | { 114 | uint16_t color = config.pal[config.palette].color[menu_pos]; 115 | uint16_t tmp; 116 | 117 | tmp = color >> 11; 118 | tmp *= 2100; 119 | tmp /= 255; 120 | hexnum[0] = (tmp & 0xF0) >> 4; 121 | hexnum[1] = tmp & 0x0F; 122 | 123 | tmp = (color >> 5) & 0x3F; 124 | tmp *= 1033; 125 | tmp /= 255; 126 | hexnum[2] = (tmp & 0xF0) >> 4; 127 | hexnum[3] = tmp & 0x0F; 128 | 129 | tmp = color & 0x1F; 130 | tmp *= 2105; 131 | tmp /= 255; 132 | hexnum[4] = (tmp & 0xF0) >> 4; 133 | hexnum[5] = tmp & 0x0F; 134 | } 135 | 136 | static void combine_palette() 137 | { 138 | uint16_t color; 139 | uint16_t tmp; 140 | 141 | tmp = (hexnum[0] << 4) | hexnum[1]; 142 | tmp *= 31; 143 | tmp /= 255; 144 | color = tmp << 11; 145 | 146 | tmp = (hexnum[2] << 4) | hexnum[3]; 147 | tmp *= 63; 148 | tmp /= 255; 149 | color |= (tmp & 0x3F) << 5; 150 | 151 | tmp = (hexnum[4] << 4) | hexnum[5]; 152 | tmp *= 31; 153 | tmp /= 255; 154 | color |= tmp & 0x1F; 155 | 156 | config.pal[config.palette].color[color_pos] = color; 157 | 158 | config_apply_palette(config.palette); 159 | } 160 | 161 | static void slowdown(uint8_t count) 162 | { 163 | do 164 | { 165 | TF0 = 0; 166 | while(!TF0); 167 | } while(--count); 168 | } 169 | 170 | // 171 | // values 172 | 173 | static void change_value() 174 | { 175 | switch(main_pos) 176 | { 177 | #ifndef ADC_BACKLIGHT 178 | case MENU_BRIGHTNESS: 179 | spicmd_backlight(gamma_table[menu_pos]); 180 | break; 181 | #endif 182 | case MENU_PALETTE: 183 | config_apply_palette(menu_pos); 184 | break; 185 | case MENU_COLORS: 186 | fake_palette(); 187 | color_pos = menu_pos; 188 | // do not write 189 | return; 190 | case MENU_GRID: 191 | spicmd_show_grid(menu_pos); 192 | break; 193 | case MENU_POS_H: 194 | spicmd_h_position(menu_pos); 195 | break; 196 | case MENU_POS_V: 197 | spicmd_v_position(menu_pos); 198 | break; 199 | } 200 | 201 | *value_ptr[main_pos] = menu_pos; 202 | } 203 | 204 | static void setup_value() 205 | { 206 | menu_icon_i = 0; 207 | 208 | menu_max = menu_side_limit[menu_pos]; 209 | 210 | if(main_pos == MENU_COLORS) 211 | { 212 | menu_pos = color_pos; 213 | fake_palette(); 214 | return; 215 | } 216 | #ifndef ADC_BACKLIGHT 217 | if(main_pos == MENU_BRIGHTNESS) 218 | menu_icon_i = 1; 219 | #endif 220 | menu_pos = *value_ptr[main_pos]; 221 | } 222 | 223 | // 224 | // partial stuff 225 | 226 | static void put_number(int16_t x, int8_t y, uint8_t num) 227 | { 228 | uint8_t ten; 229 | 230 | num += menu_icon_i; 231 | 232 | ten = num / 10; 233 | num %= 10; 234 | 235 | if(!ten) 236 | { 237 | gfx_show_icon(x, y, GNUM_0 + num); 238 | // just to keep consistent draw speed 239 | gfx_show_icon(x, y, GNUM_0 + num); 240 | return; 241 | } 242 | 243 | x -= OSD_DIGIT_GAP / 2; 244 | gfx_show_icon(x, y, GNUM_0 + ten); 245 | x += OSD_DIGIT_GAP; 246 | gfx_show_icon(x, y, GNUM_0 + num); 247 | } 248 | 249 | // 250 | // draw 251 | 252 | static void draw_icons() 253 | { 254 | uint8_t tmp = menu_pos; 255 | int16_t x; 256 | 257 | x = menu_icon_x; 258 | 259 | // center 260 | gfx_show_icon(x, menu_icon_y1, menu_icon_i + tmp); 261 | 262 | // right 263 | tmp++; 264 | if(tmp >= menu_max) 265 | tmp = 0; 266 | gfx_show_icon(x + menu_icon_s, menu_icon_y0, menu_icon_i + tmp); 267 | 268 | // left 269 | tmp = menu_pos - 1; 270 | if(tmp == 0xFF) 271 | tmp = menu_max - 1; 272 | gfx_show_icon(x - menu_icon_s, menu_icon_y0, menu_icon_i + tmp); 273 | } 274 | 275 | static void draw_numbers() 276 | { 277 | uint8_t tmp = menu_pos; 278 | int16_t x; 279 | 280 | x = menu_icon_x; 281 | 282 | // center 283 | put_number(x, menu_icon_y1, tmp); 284 | 285 | // right 286 | tmp++; 287 | if(tmp >= menu_max) 288 | tmp = 0; 289 | put_number(x + menu_icon_s, menu_icon_y0, tmp); 290 | 291 | // left 292 | tmp = menu_pos - 1; 293 | if(tmp == 0xFF) 294 | tmp = menu_max - 1; 295 | put_number(x - menu_icon_s, menu_icon_y0, tmp); 296 | } 297 | 298 | static void draw_palhex() 299 | { 300 | uint8_t i = 0; 301 | uint8_t x = OSD_CENTER - 2 * OSD_DIGIT_GAP - OSD_DIGIT_GAP / 2; 302 | 303 | do 304 | { 305 | uint8_t y = menu_icon_y0; 306 | if(i == menu_pos) 307 | y = menu_icon_y1; 308 | gfx_show_icon(x, y, GNUM_0 + hexnum[i]); 309 | x += OSD_DIGIT_GAP; 310 | i++; 311 | } while(i < 6); 312 | } 313 | 314 | static void draw_main_text() 315 | { 316 | // main text Y position is bound to scroll movement 317 | uint8_t y; 318 | 319 | if(menu_icon_x < OSD_CENTER) 320 | y = OSD_CENTER - menu_icon_x; 321 | else 322 | y = menu_icon_x - OSD_CENTER; 323 | 324 | y += GAP_MAIN_ICON_TEXT; 325 | y += menu_icon_y1; 326 | 327 | gfx_show_icon(OSD_CENTER, y, MAIN_FIRST_TEXT + menu_pos); 328 | } 329 | 330 | // 331 | // ticker: number menu 332 | 333 | static void tick_color_menu() 334 | { 335 | if(!IN_LEFT) 336 | { 337 | // move 338 | uint8_t i = HEX_NUMBER_OFFSET + 1; 339 | 340 | do 341 | { 342 | i--; 343 | menu_icon_y1 = OSD_SIDE_ICON_Y - i; 344 | draw_palhex(); 345 | } while(i); 346 | 347 | menu_pos++; 348 | if(menu_pos >= 6) 349 | menu_pos = 0; 350 | 351 | do 352 | { 353 | i++; 354 | menu_icon_y1 = OSD_SIDE_ICON_Y - i; 355 | draw_palhex(); 356 | } while(i < HEX_NUMBER_OFFSET); 357 | 358 | slowdown(COLOR_BUTTON_DELAY); 359 | 360 | return; 361 | } 362 | 363 | if(!IN_RIGHT) 364 | { 365 | // move 366 | uint8_t i = HEX_NUMBER_OFFSET + 1; 367 | 368 | do 369 | { 370 | i--; 371 | menu_icon_y1 = OSD_SIDE_ICON_Y - i; 372 | draw_palhex(); 373 | } while(i); 374 | 375 | menu_pos--; 376 | if(menu_pos == 0xFF) 377 | menu_pos = 5; 378 | 379 | do 380 | { 381 | i++; 382 | menu_icon_y1 = OSD_SIDE_ICON_Y - i; 383 | draw_palhex(); 384 | } while(i < HEX_NUMBER_OFFSET); 385 | 386 | slowdown(COLOR_BUTTON_DELAY); 387 | 388 | return; 389 | } 390 | 391 | if(!IN_ENTER) 392 | { 393 | // leave submenu 394 | uint8_t i = 0; 395 | 396 | do 397 | { 398 | i++; 399 | menu_icon_y0 = OSD_SIDE_ICON_Y + i; 400 | menu_icon_y1 = OSD_SIDE_ICON_Y - HEX_NUMBER_OFFSET + i; 401 | draw_palhex(); 402 | } while(i < LEN_HEX_SCROLL); 403 | 404 | menu_pos = main_pos; 405 | menu_max = MAIN_COUNT; 406 | menu_icon_i = MAIN_FIRST_ICON; 407 | 408 | menu_icon_y0 = OSD_MAIN_ICON_Y; 409 | i = LEN_SELECT_SCROLL; 410 | do 411 | { 412 | i--; 413 | menu_icon_s = OSD_ICON_GAP + i; 414 | menu_icon_y1 = OSD_MAIN_ICON_Y - i; 415 | draw_icons(); 416 | draw_main_text(); 417 | } while(i); 418 | 419 | ticker = tick_main_menu; 420 | return; 421 | } 422 | 423 | if(!IN_TOUCH_L) 424 | { 425 | // increment 426 | uint8_t x, y, n; 427 | 428 | x = hexnum[menu_pos]; 429 | x++; 430 | x &= 0x0F; 431 | hexnum[menu_pos] = x; 432 | 433 | x = OSD_CENTER - 2 * OSD_DIGIT_GAP - OSD_DIGIT_GAP / 2; 434 | x += menu_pos * OSD_DIGIT_GAP; 435 | 436 | n = GNUM_0 + hexnum[menu_pos]; 437 | 438 | y = OSD_HEIGHT; 439 | do 440 | { 441 | y--; 442 | gfx_show_icon(x, y, n); 443 | } while(y > OSD_SIDE_ICON_Y - HEX_NUMBER_OFFSET); 444 | 445 | combine_palette(); 446 | slowdown(COLOR_BUTTON_DELAY); 447 | 448 | return; 449 | } 450 | 451 | if(!IN_TOUCH_R) 452 | { 453 | // decrement 454 | uint8_t x, y, n; 455 | 456 | x = hexnum[menu_pos]; 457 | x--; 458 | x &= 0x0F; 459 | hexnum[menu_pos] = x; 460 | 461 | x = OSD_CENTER - 2 * OSD_DIGIT_GAP - OSD_DIGIT_GAP / 2; 462 | x += menu_pos * OSD_DIGIT_GAP; 463 | 464 | n = GNUM_0 + hexnum[menu_pos]; 465 | 466 | y = OSD_HEIGHT; 467 | do 468 | { 469 | y--; 470 | gfx_show_icon(x, y, n); 471 | } while(y > OSD_SIDE_ICON_Y - HEX_NUMBER_OFFSET); 472 | 473 | combine_palette(); 474 | slowdown(COLOR_BUTTON_DELAY); 475 | 476 | return; 477 | } 478 | } 479 | 480 | // 481 | // ticker: number menu 482 | 483 | static void tick_number_menu() 484 | { 485 | if(!IN_LEFT) 486 | { 487 | // scroll left 488 | uint8_t i = 0; 489 | 490 | do 491 | { 492 | i++; 493 | menu_icon_x = OSD_CENTER - i; 494 | draw_numbers(); 495 | } while(i < OSD_ICON_GAP / 2); 496 | 497 | menu_pos++; 498 | if(menu_pos >= menu_max) 499 | menu_pos = 0; 500 | 501 | change_value(); 502 | 503 | do 504 | { 505 | i--; 506 | menu_icon_x = OSD_CENTER + i; 507 | draw_numbers(); 508 | } while(i); 509 | 510 | return; 511 | } 512 | 513 | if(!IN_RIGHT) 514 | { 515 | // scroll right 516 | uint8_t i = 0; 517 | 518 | do 519 | { 520 | i++; 521 | menu_icon_x = OSD_CENTER + i; 522 | draw_numbers(); 523 | } while(i < OSD_ICON_GAP / 2); 524 | 525 | menu_pos--; 526 | if(menu_pos == 0xFF) 527 | menu_pos = menu_max - 1; 528 | 529 | change_value(); 530 | 531 | do 532 | { 533 | i--; 534 | menu_icon_x = OSD_CENTER - i; 535 | draw_numbers(); 536 | } while(i); 537 | 538 | return; 539 | } 540 | 541 | if(!IN_ENTER) 542 | { 543 | if(main_pos == MENU_COLORS) 544 | { 545 | // enter color menu 546 | uint8_t i; 547 | 548 | i = 0; 549 | do 550 | { 551 | i++; 552 | menu_icon_s = OSD_ICON_GAP + i; 553 | menu_icon_y1 = OSD_SIDE_ICON_Y + i; 554 | draw_numbers(); 555 | } while(i < LEN_HEX_SCROLL); 556 | 557 | config_apply_palette(config.palette); 558 | split_palette(); 559 | 560 | menu_pos = 0; 561 | 562 | do 563 | { 564 | i--; 565 | menu_icon_y0 = OSD_SIDE_ICON_Y + i; 566 | menu_icon_y1 = OSD_SIDE_ICON_Y - HEX_NUMBER_OFFSET + i; 567 | draw_palhex(); 568 | } while(i); 569 | 570 | ticker = tick_color_menu; 571 | return; 572 | } else 573 | { 574 | // leave submenu 575 | uint8_t i; 576 | 577 | i = OSD_SIDE_ICON_Y; 578 | do 579 | { 580 | i++; 581 | menu_icon_y0 = i; 582 | menu_icon_y1 = i; 583 | draw_numbers(); 584 | } while(i < SIDE_Y_START); 585 | 586 | menu_pos = main_pos; 587 | menu_max = MAIN_COUNT; 588 | menu_icon_i = MAIN_FIRST_ICON; 589 | 590 | menu_icon_y0 = OSD_MAIN_ICON_Y; 591 | i = LEN_SELECT_SCROLL; 592 | do 593 | { 594 | i--; 595 | menu_icon_s = OSD_ICON_GAP + i; 596 | menu_icon_y1 = OSD_MAIN_ICON_Y - i; 597 | draw_icons(); 598 | draw_main_text(); 599 | } while(i); 600 | 601 | ticker = tick_main_menu; 602 | return; 603 | } 604 | } 605 | } 606 | 607 | // 608 | // ticker: main menu 609 | 610 | static void tick_main_menu() 611 | { 612 | if(!IN_LEFT) 613 | { 614 | // scroll left 615 | uint8_t i = 0; 616 | 617 | do 618 | { 619 | i++; 620 | menu_icon_x = OSD_CENTER - i; 621 | draw_icons(); 622 | draw_main_text(); 623 | } while(i < OSD_ICON_GAP / 2); 624 | 625 | menu_pos++; 626 | if(menu_pos >= menu_max) 627 | menu_pos = 0; 628 | 629 | do 630 | { 631 | i--; 632 | menu_icon_x = OSD_CENTER + i; 633 | draw_icons(); 634 | draw_main_text(); 635 | } while(i); 636 | 637 | return; 638 | } 639 | 640 | if(!IN_RIGHT) 641 | { 642 | // scroll right 643 | uint8_t i = 0; 644 | 645 | do 646 | { 647 | i++; 648 | menu_icon_x = OSD_CENTER + i; 649 | draw_icons(); 650 | draw_main_text(); 651 | } while(i < OSD_ICON_GAP / 2); 652 | 653 | menu_pos--; 654 | if(menu_pos == 0xFF) 655 | menu_pos = menu_max - 1; 656 | 657 | do 658 | { 659 | i--; 660 | menu_icon_x = OSD_CENTER - i; 661 | draw_icons(); 662 | draw_main_text(); 663 | } while(i); 664 | 665 | return; 666 | } 667 | 668 | if(!IN_ENTER) 669 | { 670 | // enter submenu 671 | uint8_t i = 0; 672 | 673 | do 674 | { 675 | i++; 676 | menu_icon_s = OSD_ICON_GAP + i; 677 | menu_icon_y1 = OSD_MAIN_ICON_Y - i; 678 | draw_icons(); 679 | draw_main_text(); 680 | } while(i < LEN_SELECT_SCROLL); 681 | 682 | main_pos = menu_pos; 683 | setup_value(); 684 | 685 | menu_icon_s = OSD_ICON_GAP; 686 | i = SIDE_Y_START + 1; 687 | do 688 | { 689 | i--; 690 | menu_icon_y0 = i; 691 | menu_icon_y1 = i; 692 | draw_numbers(); 693 | } while(i > OSD_SIDE_ICON_Y); 694 | 695 | ticker = tick_number_menu; 696 | return; 697 | } 698 | 699 | if(!IN_TOUCH_L && !IN_TOUCH_R) 700 | { 701 | // leave menu 702 | uint8_t i = 0; 703 | 704 | do 705 | { 706 | i++; 707 | menu_icon_s = OSD_ICON_GAP + i; 708 | menu_icon_y1 = OSD_MAIN_ICON_Y + i; 709 | draw_icons(); 710 | draw_main_text(); 711 | } while(i < MAIN_SELECT_SCROLL); 712 | 713 | config_save(); 714 | slowdown(100); 715 | 716 | OUT_OSD = 1; 717 | 718 | slowdown(500); 719 | 720 | ticker = tick_osd_idle; 721 | return; 722 | } 723 | } 724 | 725 | // 726 | // ticker: idle 727 | 728 | static void tick_osd_idle() 729 | { 730 | static __xdata uint8_t old = 0xFF; 731 | uint8_t in; 732 | 733 | if(menu_save_wait) 734 | { 735 | menu_save_wait--; 736 | if(!menu_save_wait) 737 | config_save(); 738 | } 739 | 740 | in = IN_TOUCH_L; 741 | in |= IN_TOUCH_R << 1; 742 | 743 | if(old != 0xFF) 744 | { 745 | uint8_t check = in ^ old; 746 | 747 | if(!check) 748 | return; 749 | 750 | check &= in; 751 | if(check) 752 | { 753 | switch(old) 754 | { 755 | case 0: 756 | { 757 | // enter main menu 758 | uint8_t i = MAIN_SELECT_SCROLL; 759 | 760 | menu_max = MAIN_COUNT; 761 | menu_icon_i = MAIN_FIRST_ICON; 762 | menu_icon_x = OSD_CENTER; 763 | menu_icon_y0 = OSD_MAIN_ICON_Y; 764 | menu_icon_y1 = OSD_MAIN_ICON_Y; 765 | menu_icon_s = OSD_ICON_GAP; 766 | 767 | menu_save_wait = 0; 768 | old = 0xFF; 769 | 770 | OUT_OSD = 0; 771 | 772 | do 773 | { 774 | i--; 775 | menu_icon_s = OSD_ICON_GAP + i; 776 | menu_icon_y1 = OSD_MAIN_ICON_Y + i; 777 | draw_icons(); 778 | draw_main_text(); 779 | } while(i); 780 | 781 | ticker = tick_main_menu; 782 | return; 783 | } 784 | break; 785 | case 1: 786 | menu_save_wait = QUICK_SAVE_DELAY; 787 | config.palette++; 788 | if(config.palette >= CFG_PALETTE_COUNT) 789 | config.palette = 0; 790 | config_apply_palette(config.palette); 791 | break; 792 | case 2: 793 | menu_save_wait = QUICK_SAVE_DELAY; 794 | #ifdef ADC_BACKLIGHT 795 | config.palette--; 796 | if(config.palette == 0xFF) 797 | config.palette = CFG_PALETTE_COUNT - 1; 798 | config_apply_palette(config.palette); 799 | #else 800 | config.brightness++; 801 | if(config.brightness >= CFG_BACKLIGHT_LEVELS) 802 | config.brightness = 0; 803 | spicmd_backlight(gamma_table[config.brightness]); 804 | #endif 805 | break; 806 | } 807 | } 808 | } 809 | 810 | old = in; 811 | } 812 | 813 | // 814 | // API 815 | 816 | void osd_init() 817 | { 818 | // hide OSD 819 | OUT_OSD = 1; 820 | 821 | // for grayscale 822 | spicmd.palette.color[0] = RGB16(160, 160, 160); 823 | spicmd_palette_osd(); 824 | 825 | // background input 826 | ticker = tick_osd_idle; 827 | 828 | // menu background 829 | spicmd_osd_clear(OSD_COLOR_BACKGROUND); 830 | #ifdef OSD_COLOR_TOP 831 | spicmd_osd_line(0, OSD_COLOR_TOP); 832 | #endif 833 | #ifdef OSD_COLOR_BOT 834 | spicmd_osd_line(OSD_HEIGHT - 1, OSD_COLOR_BOT); 835 | #endif 836 | } 837 | 838 | --------------------------------------------------------------------------------