├── firm ├── .gitignore ├── include │ ├── coin.h │ ├── settings.h │ ├── rsrc.h │ ├── futaba.h │ └── README ├── .vscode │ ├── extensions.json │ ├── launch.json │ └── c_cpp_properties.json ├── test │ └── README ├── platformio.ini ├── src │ ├── settings.cpp │ ├── coin.cpp │ ├── futaba.cpp │ ├── main.cpp │ └── rsrc.cpp └── lib │ └── README ├── sch ├── schematic.dch └── schematic.png └── README.md /firm/.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | -------------------------------------------------------------------------------- /sch/schematic.dch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladkorotnev/wacca-vfd-arduino/HEAD/sch/schematic.dch -------------------------------------------------------------------------------- /sch/schematic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladkorotnev/wacca-vfd-arduino/HEAD/sch/schematic.png -------------------------------------------------------------------------------- /firm/include/coin.h: -------------------------------------------------------------------------------- 1 | #ifndef COIN_H_ 2 | #define COIN_H_ 3 | #include 4 | 5 | void coin_begin(); 6 | void coin_set_callback(void (*fun_ptr)(uint32_t, uint32_t)); 7 | void coin_save_if_needed(); 8 | uint32_t coin_get(); 9 | uint32_t coin_get_amt(); 10 | bool coin_reset_if_needed(); 11 | 12 | #endif -------------------------------------------------------------------------------- /firm/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "platformio.platformio-ide" 6 | ], 7 | "unwantedRecommendations": [ 8 | "ms-vscode.cpptools-extension-pack" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /firm/include/settings.h: -------------------------------------------------------------------------------- 1 | #ifndef SETTINGS_H_ 2 | #define SETTINGS_H_ 3 | #include 4 | 5 | void cfg_begin(); 6 | 7 | typedef enum cfg_index { 8 | CFG_READY = 0, 9 | COIN_COUNT = 1, 10 | MAX_INVALID 11 | } cfg_index_t; 12 | 13 | bool cfg_valid(); 14 | int32_t cfg_read(cfg_index_t); 15 | void cfg_write(cfg_index_t, int32_t value); 16 | 17 | #endif -------------------------------------------------------------------------------- /firm/include/rsrc.h: -------------------------------------------------------------------------------- 1 | #ifndef RSRC_H_ 2 | #define RSRC_H_ 3 | #include 4 | 5 | typedef struct { 6 | bool is_progmem; 7 | const unsigned char * data; 8 | uint16_t width_pixels; 9 | uint8_t height_strides; 10 | bool inverse; 11 | } img_data_t; 12 | 13 | size_t img_offset_at(img_data_t * image, uint16_t left, uint8_t top); 14 | img_data_t img_invert(img_data_t * image); 15 | 16 | extern img_data_t IMG_WOCAO; 17 | extern img_data_t IMG_TANOC; 18 | 19 | #endif -------------------------------------------------------------------------------- /firm/test/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for PlatformIO Test Runner and project tests. 3 | 4 | Unit Testing is a software testing method by which individual units of 5 | source code, sets of one or more MCU program modules together with associated 6 | control data, usage procedures, and operating procedures, are tested to 7 | determine whether they are fit for use. Unit testing finds problems early 8 | in the development cycle. 9 | 10 | More information about PlatformIO Unit Testing: 11 | - https://docs.platformio.org/en/latest/advanced/unit-testing/index.html 12 | -------------------------------------------------------------------------------- /firm/platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [env:sparkfun_promicro16] 12 | platform = atmelavr 13 | board = sparkfun_promicro16 14 | upload_port = COM8 15 | monitor_port = COM8 16 | monitor_speed = 9600 17 | framework = arduino 18 | build_flags = -DSTARTUP_DELAY=1000 19 | lib_deps = 20 | prosenb/EEPROMWearLevel@^2.1.0 21 | -------------------------------------------------------------------------------- /firm/src/settings.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define SETTINGS_VERSION 0 6 | #define SETTINGS_MAGIC 0x3939 7 | 8 | void cfg_begin() { 9 | EEPROMwl.begin(SETTINGS_VERSION, cfg_index::MAX_INVALID); 10 | if(!cfg_valid()) { 11 | cfg_write(cfg_index::COIN_COUNT, 0); 12 | cfg_write(cfg_index::CFG_READY, SETTINGS_MAGIC); 13 | } 14 | } 15 | 16 | int32_t cfg_read(cfg_index_t idx) { 17 | int32_t tmp = -1; 18 | EEPROMwl.get(idx, tmp); 19 | return tmp; 20 | } 21 | 22 | void cfg_write(cfg_index_t idx, int32_t value) { 23 | EEPROMwl.put(idx, value); 24 | } 25 | 26 | bool cfg_valid() { 27 | return cfg_read(cfg_index::CFG_READY) == SETTINGS_MAGIC; 28 | } -------------------------------------------------------------------------------- /firm/include/futaba.h: -------------------------------------------------------------------------------- 1 | #ifndef FUTABA_H_ 2 | #define FUTABA_H_ 3 | #include 4 | 5 | void ftb_init(); 6 | void ftb_reset(); 7 | void ftb_clear(); 8 | void ftb_power(bool on); 9 | 10 | typedef enum { 11 | BRIGHT_0 = 0, 12 | BRIGHT_25 = 1, 13 | BRIGHT_50 = 2, 14 | BRIGHT_75 = 3, 15 | BRIGHT_100 = 4 16 | } ftb_bright_t; 17 | 18 | void ftb_brightness(ftb_bright_t); 19 | 20 | void ftb_canvas_shift(uint16_t left); 21 | void ftb_draw_image(img_data_t * image, uint16_t left, uint8_t top); 22 | 23 | void ftb_cursor(uint16_t left, uint8_t top); 24 | 25 | typedef enum { 26 | SIMP_CHINESE, 27 | TRAD_CHINESE, 28 | JAPANESE, 29 | KOREAN 30 | } ftb_lang_t; 31 | void ftb_language(ftb_lang_t); 32 | 33 | typedef enum { 34 | FONT_16_16, 35 | FONT_6_8 36 | } ftb_font_size_t; 37 | void ftb_font_size(ftb_font_size_t); 38 | 39 | void ftb_scroll_box_make(uint16_t left, uint8_t top, uint16_t width, uint8_t height); 40 | void ftb_scroll_speed(uint8_t divisor); 41 | void ftb_scroll_text(const char * text); 42 | void ftb_scroll_start(); 43 | 44 | void ftb_write(const char * text); 45 | #endif -------------------------------------------------------------------------------- /firm/lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into executable file. 4 | 5 | The source code of each library should be placed in a an own separate directory 6 | ("lib/your_library_name/[here are source files]"). 7 | 8 | For example, see a structure of the following two libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | and a contents of `src/main.c`: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wacca-vfd-arduino 2 | 3 | Controls the VFD display of WACCA cabinet from an Arduino. 4 | 5 | [example video](https://twitter.com/akasaka_spk/status/1659505635078311936) 6 | 7 | ## Schematic 8 | 9 | ![schematic](sch/schematic.png) 10 | 11 | Rx,Tx of Arduino Pro Micro -> MAX232 -> Tx,Rx of VFD display (COM2 cable on WACCA cabinet) 12 | 13 | Pin 2 of Arduino -> Coin selector (N-O contact) 14 | 15 | Gnd of Arduino -> Coin selector (COM contact) 16 | 17 | For other connections around MAX232 see tutorials online such as the one in [DIYODE Magazine](https://diyodemag.com/education/the_classroom_rs232_the_max232_ic_arduino_uno). 18 | 19 | For RS232 pinout see e.g. [PinoutGuide](https://pinoutguide.com/SerialPorts/Serial9_pinout.shtml) 20 | 21 | ## Thanks 22 | 23 | Thanks to Obiwan for helping with this protocol. 24 | 25 | Thanks to [image2cpp](https://javl.github.io/image2cpp/) for helping to convert the graphics. 26 | 27 | ## Graphics Data Format 28 | 29 | 1 bit, recorded as bytes where each byte is a quarter column. Thus the screen is laid out like this: 30 | 31 | ``` 32 | [0][4][8] 33 | [1][5][9] 34 | [2][6]... 35 | [3][7]... 36 | ``` 37 | 38 | When converting on image2cpp you can upload a horizontal image and then enable *Rotate: 90* and *Flip: horizontally* to get the correct data bytes. -------------------------------------------------------------------------------- /firm/include/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project header files. 3 | 4 | A header file is a file containing C declarations and macro definitions 5 | to be shared between several project source files. You request the use of a 6 | header file in your project source file (C, C++, etc) located in `src` folder 7 | by including it, with the C preprocessing directive `#include'. 8 | 9 | ```src/main.c 10 | 11 | #include "header.h" 12 | 13 | int main (void) 14 | { 15 | ... 16 | } 17 | ``` 18 | 19 | Including a header file produces the same results as copying the header file 20 | into each source file that needs it. Such copying would be time-consuming 21 | and error-prone. With a header file, the related declarations appear 22 | in only one place. If they need to be changed, they can be changed in one 23 | place, and programs that include the header file will automatically use the 24 | new version when next recompiled. The header file eliminates the labor of 25 | finding and changing all the copies as well as the risk that a failure to 26 | find one copy will result in inconsistencies within a program. 27 | 28 | In C, the usual convention is to give header files names that end with `.h'. 29 | It is most portable to use only letters, digits, dashes, and underscores in 30 | header file names, and at most one dot. 31 | 32 | Read more about using header files in official GCC documentation: 33 | 34 | * Include Syntax 35 | * Include Operation 36 | * Once-Only Headers 37 | * Computed Includes 38 | 39 | https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html 40 | -------------------------------------------------------------------------------- /firm/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // AUTOMATICALLY GENERATED FILE. PLEASE DO NOT MODIFY IT MANUALLY 2 | // 3 | // PIO Unified Debugger 4 | // 5 | // Documentation: https://docs.platformio.org/page/plus/debugging.html 6 | // Configuration: https://docs.platformio.org/page/projectconf/section_env_debug.html 7 | 8 | { 9 | "version": "0.2.0", 10 | "configurations": [ 11 | { 12 | "type": "platformio-debug", 13 | "request": "launch", 14 | "name": "PIO Debug", 15 | "executable": "d:/Code/wacca-vfd/firm/.pio/build/sparkfun_promicro8/firmware.elf", 16 | "projectEnvName": "sparkfun_promicro8", 17 | "toolchainBinDir": "C:/Users/akasaka/.platformio/packages/toolchain-atmelavr/bin", 18 | "internalConsoleOptions": "openOnSessionStart", 19 | "preLaunchTask": { 20 | "type": "PlatformIO", 21 | "task": "Pre-Debug" 22 | } 23 | }, 24 | { 25 | "type": "platformio-debug", 26 | "request": "launch", 27 | "name": "PIO Debug (skip Pre-Debug)", 28 | "executable": "d:/Code/wacca-vfd/firm/.pio/build/sparkfun_promicro8/firmware.elf", 29 | "projectEnvName": "sparkfun_promicro8", 30 | "toolchainBinDir": "C:/Users/akasaka/.platformio/packages/toolchain-atmelavr/bin", 31 | "internalConsoleOptions": "openOnSessionStart" 32 | }, 33 | { 34 | "type": "platformio-debug", 35 | "request": "launch", 36 | "name": "PIO Debug (without uploading)", 37 | "executable": "d:/Code/wacca-vfd/firm/.pio/build/sparkfun_promicro8/firmware.elf", 38 | "projectEnvName": "sparkfun_promicro8", 39 | "toolchainBinDir": "C:/Users/akasaka/.platformio/packages/toolchain-atmelavr/bin", 40 | "internalConsoleOptions": "openOnSessionStart", 41 | "loadMode": "manual" 42 | } 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /firm/src/coin.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define COIN_PIN 2 6 | #define COIN_DENOMINATION 100 7 | 8 | static volatile int32_t coin_count_cache = 0; 9 | static volatile bool coin_need_save = false; 10 | static void (*callback)(uint32_t, uint32_t) = nullptr; 11 | 12 | void coin_call_callback() { 13 | if(callback != nullptr && coin_count_cache > 0) { 14 | callback(coin_count_cache, coin_count_cache * COIN_DENOMINATION); 15 | } 16 | } 17 | 18 | void coin_ISR() { 19 | static unsigned long debounce_timer = 0; 20 | unsigned long now_timer = millis(); 21 | if(now_timer - debounce_timer > 200) { 22 | coin_count_cache++; 23 | coin_need_save = true; 24 | coin_call_callback(); 25 | debounce_timer = now_timer; 26 | } 27 | } 28 | 29 | void coin_begin() { 30 | callback = nullptr; 31 | pinMode(COIN_PIN, INPUT_PULLUP); 32 | if(cfg_valid()) { 33 | coin_count_cache = cfg_read(cfg_index::COIN_COUNT); 34 | } 35 | } 36 | 37 | bool coin_reset_if_needed() { 38 | bool rslt = false; 39 | if(digitalRead(COIN_PIN) == LOW) { 40 | int millis_start = millis(); 41 | while(digitalRead(COIN_PIN) == LOW) delay(1); 42 | if(millis() - millis_start > 2000) { 43 | cfg_write(cfg_index::COIN_COUNT, 0); 44 | rslt = true; 45 | } 46 | } 47 | 48 | return rslt; 49 | } 50 | 51 | bool haveInterrupt = false; 52 | void coin_set_callback(void (*fun_ptr)(uint32_t, uint32_t)) { 53 | if(!haveInterrupt) { 54 | attachInterrupt(digitalPinToInterrupt(COIN_PIN), coin_ISR, LOW); 55 | haveInterrupt = true; 56 | } 57 | callback = fun_ptr; 58 | } 59 | 60 | 61 | void coin_save_if_needed() { 62 | if(coin_need_save) { 63 | cli(); 64 | cfg_write(cfg_index_t::COIN_COUNT, coin_count_cache); 65 | coin_need_save = false; 66 | sei(); 67 | } 68 | } 69 | 70 | uint32_t coin_get() { 71 | return coin_count_cache; 72 | } 73 | 74 | uint32_t coin_get_amt() { 75 | return coin_get() * COIN_DENOMINATION; 76 | } -------------------------------------------------------------------------------- /firm/src/futaba.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | // 5 | // Thanks to 6 | // OBIWAN 7 | // for help with this protocol 8 | // 9 | 10 | #ifndef FTB_PORT 11 | #define FTB_PORT Serial1 12 | #endif 13 | 14 | #define LEFT_HI(x) (((x) & 0x100) >> 8) 15 | #define LEFT_LO(x) ((x) & 0xFF) 16 | 17 | #define FTB_PORT_WRITE_LEFT(x) {FTB_PORT.write(LEFT_HI(x)); FTB_PORT.write(LEFT_LO(x));} 18 | 19 | void ftb_init() { 20 | FTB_PORT.begin(115200); 21 | } 22 | 23 | void ftb_reset() { 24 | FTB_PORT.write("\x1B\x0B"); // reset 25 | } 26 | 27 | void ftb_clear() { 28 | FTB_PORT.write("\x1B\x0C"); 29 | } 30 | 31 | void ftb_power(bool on) { 32 | FTB_PORT.write("\x1B\x21"); // on 33 | FTB_PORT.write(on ? 0x1 : 0x0); 34 | } 35 | 36 | void ftb_brightness(ftb_bright_t brightness) { 37 | FTB_PORT.write("\x1b\x20"); 38 | FTB_PORT.write((char) brightness); 39 | } 40 | 41 | void ftb_canvas_shift(uint16_t left) { 42 | FTB_PORT.write("\x1B\x22"); 43 | FTB_PORT_WRITE_LEFT(left); 44 | } 45 | 46 | void ftb_cursor(uint16_t left, uint8_t top) { 47 | FTB_PORT.write("\x1B\x30"); // cursor set 48 | FTB_PORT_WRITE_LEFT(left); 49 | FTB_PORT.write(top); 50 | } 51 | 52 | void ftb_language(ftb_lang_t lang) { 53 | FTB_PORT.write("\x1B\x32"); 54 | FTB_PORT.write((char) lang); 55 | } 56 | 57 | void ftb_font_size(ftb_font_size_t font) { 58 | FTB_PORT.write("\x1B\x33"); 59 | FTB_PORT.write((char) font); 60 | } 61 | 62 | void ftb_scroll_box_make(uint16_t left, uint8_t top, uint16_t width, uint8_t height) { 63 | FTB_PORT.write("\x1B\x40"); 64 | FTB_PORT_WRITE_LEFT(left); 65 | FTB_PORT.write(top); 66 | FTB_PORT_WRITE_LEFT(width); 67 | FTB_PORT.write(height); 68 | } 69 | 70 | void ftb_scroll_speed(uint8_t divisor) { 71 | FTB_PORT.write("\x1B\x41"); 72 | FTB_PORT.write((char) divisor); 73 | } 74 | 75 | void ftb_scroll_text(const char * text) { 76 | size_t len = strlen(text); 77 | if(len > 255) return; 78 | 79 | FTB_PORT.write("\x1B\x50"); 80 | FTB_PORT.write(len); 81 | FTB_PORT.write(text); 82 | } 83 | 84 | void ftb_scroll_start() { 85 | FTB_PORT.write("\x1B\x51"); 86 | } 87 | 88 | void ftb_write(const char * text) { 89 | FTB_PORT.write(text); 90 | } 91 | 92 | void ftb_draw_image(img_data_t * image, uint16_t left, uint8_t top) { 93 | FTB_PORT.write("\x1B\x2E"); // bmp out 94 | FTB_PORT_WRITE_LEFT(left); 95 | FTB_PORT.write(top); 96 | FTB_PORT_WRITE_LEFT(image->width_pixels - 1); 97 | FTB_PORT.write(image->height_strides - 1); 98 | 99 | for(unsigned int i = 0; i < (image->width_pixels) * (image->height_strides); i++) { 100 | byte c; 101 | if(image->is_progmem) { 102 | c = pgm_read_byte(&image->data[i]); 103 | } else { 104 | c = image->data[i]; 105 | } 106 | 107 | if(image->inverse) { 108 | c = ~c; 109 | } 110 | 111 | FTB_PORT.write(c); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /firm/.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | // 2 | // !!! WARNING !!! AUTO-GENERATED FILE! 3 | // PLEASE DO NOT MODIFY IT AND USE "platformio.ini": 4 | // https://docs.platformio.org/page/projectconf/section_env_build.html#build-flags 5 | // 6 | { 7 | "configurations": [ 8 | { 9 | "name": "PlatformIO", 10 | "includePath": [ 11 | "d:/Code/wacca-vfd/firm/include", 12 | "d:/Code/wacca-vfd/firm/src", 13 | "d:/Code/wacca-vfd/firm/.pio/libdeps/sparkfun_promicro8/EEPROMWearLevel/src", 14 | "C:/Users/akasaka/.platformio/packages/framework-arduino-avr/libraries/EEPROM/src", 15 | "C:/Users/akasaka/.platformio/packages/framework-arduino-avr/cores/arduino", 16 | "C:/Users/akasaka/.platformio/packages/framework-arduino-avr/variants/sparkfun_promicro", 17 | "C:/Users/akasaka/.platformio/packages/framework-arduino-avr/libraries/HID/src", 18 | "C:/Users/akasaka/.platformio/packages/framework-arduino-avr/libraries/SPI/src", 19 | "C:/Users/akasaka/.platformio/packages/framework-arduino-avr/libraries/SoftwareSerial/src", 20 | "C:/Users/akasaka/.platformio/packages/framework-arduino-avr/libraries/Wire/src", 21 | "" 22 | ], 23 | "browse": { 24 | "limitSymbolsToIncludedHeaders": true, 25 | "path": [ 26 | "d:/Code/wacca-vfd/firm/include", 27 | "d:/Code/wacca-vfd/firm/src", 28 | "d:/Code/wacca-vfd/firm/.pio/libdeps/sparkfun_promicro8/EEPROMWearLevel/src", 29 | "C:/Users/akasaka/.platformio/packages/framework-arduino-avr/libraries/EEPROM/src", 30 | "C:/Users/akasaka/.platformio/packages/framework-arduino-avr/cores/arduino", 31 | "C:/Users/akasaka/.platformio/packages/framework-arduino-avr/variants/sparkfun_promicro", 32 | "C:/Users/akasaka/.platformio/packages/framework-arduino-avr/libraries/HID/src", 33 | "C:/Users/akasaka/.platformio/packages/framework-arduino-avr/libraries/SPI/src", 34 | "C:/Users/akasaka/.platformio/packages/framework-arduino-avr/libraries/SoftwareSerial/src", 35 | "C:/Users/akasaka/.platformio/packages/framework-arduino-avr/libraries/Wire/src", 36 | "" 37 | ] 38 | }, 39 | "defines": [ 40 | "PLATFORMIO=60107", 41 | "ARDUINO_AVR_PROMICRO8", 42 | "F_CPU=8000000L", 43 | "ARDUINO_ARCH_AVR", 44 | "ARDUINO=10808", 45 | "USB_VID=0x1B4F", 46 | "USB_PID=0x9203", 47 | "USB_PRODUCT=\"SparkFun Pro Micro\"", 48 | "USB_MANUFACTURER=\"SparkFun\"", 49 | "__AVR_ATmega32U4__", 50 | "" 51 | ], 52 | "cStandard": "c11", 53 | "cppStandard": "c++11", 54 | "compilerPath": "C:/Users/akasaka/.platformio/packages/toolchain-atmelavr/bin/avr-gcc.exe", 55 | "compilerArgs": [ 56 | "-mmcu=atmega32u4", 57 | "" 58 | ] 59 | } 60 | ], 61 | "version": 4 62 | } 63 | -------------------------------------------------------------------------------- /firm/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | extern "C" { 8 | void setup(); 9 | void loop(); 10 | } 11 | 12 | volatile uint16_t time_coin_remain = 0; 13 | volatile uint32_t coin_amount = 0; 14 | uint32_t coin_disp_amount = 0; 15 | volatile uint16_t coin_combo = 0; 16 | 17 | void coin_callback(uint32_t count, uint32_t amount) { 18 | time_coin_remain = 5000; 19 | coin_combo++; 20 | coin_amount = amount; 21 | } 22 | 23 | bool task_coin() { 24 | if(time_coin_remain == 0) return false; 25 | 26 | time_coin_remain--; 27 | 28 | if(coin_disp_amount != coin_amount) { 29 | ftb_canvas_shift(0); 30 | char buf[21] = { 0 }; 31 | ftb_cursor(0, 0); 32 | if(coin_combo < 2) { 33 | ftb_write("** SAVE SOME CASH **"); 34 | } else { 35 | ftb_write("** "); 36 | sprintf(buf, "%u COMBO", coin_combo); 37 | ftb_write(buf); 38 | ftb_write(" **"); 39 | memset(buf, 0, 21); 40 | } 41 | ftb_cursor(0, 2); 42 | sprintf(buf, "TOTAL: %lu \\", coin_amount); 43 | ftb_write(buf); 44 | coin_disp_amount = coin_amount; 45 | } 46 | 47 | if(time_coin_remain == 0) { 48 | coin_combo = 0; 49 | ftb_cursor(0, 0); 50 | ftb_write(" "); 51 | ftb_cursor(0, 2); 52 | ftb_write(" "); 53 | } 54 | 55 | return true; 56 | } 57 | 58 | void setup() { 59 | cfg_begin(); 60 | coin_begin(); 61 | 62 | #ifdef STARTUP_DELAY 63 | delay(STARTUP_DELAY); 64 | #endif 65 | 66 | ftb_init(); 67 | 68 | ftb_reset(); 69 | ftb_power(true); 70 | ftb_brightness(BRIGHT_50); 71 | 72 | ftb_canvas_shift(0); 73 | ftb_cursor(0, 0); 74 | ftb_font_size(FONT_16_16); 75 | 76 | bool coin_did_reset = coin_reset_if_needed(); 77 | if(coin_did_reset) { 78 | ftb_cursor(0, 0); 79 | ftb_write("** COIN COUNT CLR **"); 80 | delay(2000); 81 | ftb_cursor(0, 0); 82 | ftb_write(" "); 83 | } 84 | 85 | coin_set_callback(coin_callback); 86 | } 87 | 88 | typedef enum { 89 | LOGO_IN, 90 | LOGO_STAY, 91 | LOGO_OUT, 92 | TANOC_IN, 93 | TANOC_STAY, 94 | TANOC_OUT, 95 | AMT_IN, 96 | AMT_STAY, 97 | AMT_OUT, 98 | } idle_phase; 99 | 100 | idle_phase now_phase = LOGO_IN; 101 | uint32_t logo_left = 0; 102 | uint32_t logo_time = 0; 103 | 104 | void task_idle() { 105 | switch(now_phase) { 106 | case LOGO_IN: 107 | case TANOC_IN: 108 | if(logo_left == 0) { 109 | ftb_cursor(0, 0); 110 | ftb_draw_image(now_phase == LOGO_IN ? &IMG_WOCAO : &IMG_TANOC, 160, 0); 111 | } else { 112 | ftb_canvas_shift(logo_left); 113 | } 114 | logo_left++; 115 | if(logo_left == 161) { 116 | now_phase = (now_phase == LOGO_IN ? LOGO_STAY : TANOC_STAY); 117 | logo_time = 1000; 118 | } 119 | break; 120 | 121 | case AMT_IN: 122 | if(logo_left == 0) { 123 | char buf1[21] = {0}; 124 | char buf2[21] = {0}; 125 | sprintf(buf1, "%lu coins", coin_get()); 126 | sprintf(buf2, "Total: %lu \\", coin_get_amt()); 127 | ftb_cursor(160, 0); 128 | ftb_write(" "); 129 | ftb_cursor(160, 0); 130 | ftb_write(buf1); 131 | ftb_cursor(160, 2); 132 | ftb_write(" "); 133 | ftb_cursor(160, 2); 134 | ftb_write(buf2); 135 | } else { 136 | ftb_canvas_shift(logo_left); 137 | } 138 | logo_left++; 139 | if(logo_left == 161) { 140 | now_phase = AMT_STAY; 141 | logo_time = 1000; 142 | } 143 | break; 144 | 145 | case LOGO_STAY: 146 | case TANOC_STAY: 147 | case AMT_STAY: 148 | logo_time--; 149 | if(logo_time == 0) { 150 | switch(now_phase) { 151 | case LOGO_STAY: now_phase = LOGO_OUT; break; 152 | case TANOC_STAY: now_phase = TANOC_OUT; break; 153 | case AMT_STAY: now_phase = AMT_OUT; break; 154 | } 155 | } 156 | break; 157 | 158 | case LOGO_OUT: 159 | case TANOC_OUT: 160 | case AMT_OUT: 161 | ftb_canvas_shift(logo_left); 162 | logo_left++; 163 | if(logo_left == 160*2 + 1) { 164 | switch(now_phase) { 165 | case LOGO_OUT: now_phase = TANOC_IN; break; 166 | case TANOC_OUT: now_phase = AMT_IN; break; 167 | case AMT_OUT: now_phase = LOGO_IN; break; 168 | } 169 | logo_left = 0; 170 | } 171 | break; 172 | } 173 | } 174 | 175 | unsigned long last_millis = 0; 176 | void loop() { 177 | unsigned long now_time = millis(); 178 | if(now_time - last_millis > 1) { 179 | last_millis = now_time; 180 | if(task_coin()) { 181 | now_phase = LOGO_IN; 182 | logo_left = 0; 183 | coin_save_if_needed(); 184 | return; 185 | } 186 | 187 | task_idle(); 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /firm/src/rsrc.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | const unsigned char bmp_wocao [] PROGMEM = { 4 | // 'wocao', 32x160px 5 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 6 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 7 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 8 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 9 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 10 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, 0xff, 11 | 0xf8, 0x3f, 0xff, 0xff, 0xf8, 0x0f, 0xff, 0xff, 0xf8, 0x03, 0xff, 0xff, 0xf8, 0x00, 0xff, 0xff, 12 | 0xf8, 0x00, 0x3f, 0xff, 0xfc, 0x00, 0x0f, 0xff, 0xff, 0x00, 0x03, 0xff, 0xf8, 0xc0, 0x00, 0xff, 13 | 0xf8, 0x38, 0x00, 0x1f, 0xf8, 0x0e, 0x00, 0x07, 0xf8, 0x03, 0x80, 0x01, 0xf8, 0x00, 0xe0, 0x00, 14 | 0xf8, 0x00, 0x38, 0x00, 0xf8, 0x00, 0x06, 0x00, 0xf8, 0x00, 0x01, 0x80, 0xf8, 0x00, 0x00, 0x61, 15 | 0xf8, 0x00, 0x00, 0x1f, 0xf8, 0x00, 0x00, 0x1f, 0xf8, 0x00, 0x00, 0x7f, 0xf8, 0x00, 0x01, 0xff, 16 | 0xf8, 0x00, 0x07, 0xff, 0xf8, 0x00, 0x03, 0xff, 0xfe, 0x00, 0x00, 0xff, 0xff, 0xc0, 0x00, 0x3f, 17 | 0xff, 0xe0, 0x00, 0x1f, 0xff, 0x80, 0x00, 0x3f, 0xfe, 0x00, 0x00, 0xcf, 0xf8, 0x00, 0x07, 0x0f, 18 | 0xf8, 0x00, 0x1c, 0x0f, 0xf8, 0x00, 0x70, 0x0f, 0xf8, 0x01, 0xc0, 0x0f, 0xf8, 0x06, 0x00, 0x0f, 19 | 0xf8, 0x18, 0x00, 0x0f, 0xf8, 0x60, 0x00, 0x0f, 0xf9, 0x80, 0x00, 0x3f, 0xfe, 0x00, 0x01, 0xff, 20 | 0xfc, 0x00, 0x03, 0xff, 0xe6, 0x00, 0x00, 0xfb, 0xc1, 0x80, 0x00, 0x39, 0x00, 0x70, 0x00, 0x08, 21 | 0x00, 0xfc, 0x00, 0x08, 0x01, 0xff, 0x00, 0x08, 0x07, 0xff, 0xc0, 0x08, 0x0f, 0xff, 0xf0, 0x08, 22 | 0x1f, 0xf8, 0x0c, 0x0c, 0x3f, 0xe0, 0x03, 0x0e, 0x7f, 0x80, 0x00, 0xcf, 0x7f, 0x03, 0xe0, 0x7f, 23 | 0xfe, 0x18, 0x0c, 0x3f, 0xfc, 0x20, 0x02, 0x1f, 0xf8, 0x40, 0x01, 0x0f, 0xf8, 0x80, 0x00, 0x8f, 24 | 0xf0, 0x03, 0xe0, 0x07, 0xf1, 0x07, 0xf0, 0x47, 0xf0, 0x0f, 0xf8, 0x07, 0xf2, 0x0f, 0xfc, 0x23, 25 | 0xe2, 0x1f, 0xfc, 0x23, 0xe2, 0x1f, 0xfc, 0x23, 0xe2, 0x1f, 0xfc, 0x23, 0xf0, 0x0f, 0xf8, 0x03, 26 | 0xf1, 0x07, 0xf8, 0x47, 0xf1, 0x07, 0xf0, 0x47, 0xf8, 0x87, 0xf0, 0x87, 0xf8, 0x07, 0xf0, 0x0f, 27 | 0xfc, 0x04, 0x10, 0x1f, 0xfc, 0x04, 0x10, 0x1f, 0xfe, 0x04, 0x10, 0x3f, 0x7f, 0x87, 0xf0, 0x7f, 28 | 0x3e, 0xc4, 0x11, 0xbe, 0x3c, 0x3c, 0x1e, 0x1e, 0x1c, 0x4c, 0x19, 0x1c, 0x08, 0x03, 0xe0, 0x88, 29 | 0x08, 0x8f, 0xf8, 0x88, 0x08, 0x0f, 0xf8, 0x08, 0x11, 0x1f, 0xfc, 0x40, 0x91, 0x1f, 0xfc, 0x40, 30 | 0xd1, 0x1f, 0xfc, 0x41, 0xf1, 0x1f, 0xfc, 0x47, 0xf1, 0x1f, 0xfc, 0x47, 0xf8, 0x8f, 0xf8, 0x8f, 31 | 0xf8, 0x87, 0xf0, 0x8f, 0xf8, 0x43, 0xe1, 0x0f, 0xfc, 0x03, 0xe0, 0x1f, 0xfe, 0x03, 0xe0, 0x3f, 32 | 0xfe, 0x03, 0xe0, 0x3f, 0xff, 0x03, 0xe0, 0x7f, 0xff, 0xc3, 0xe1, 0xcf, 0xff, 0xf3, 0xe7, 0x0f, 33 | 0xff, 0xff, 0xfc, 0x0f, 0xff, 0xff, 0xf0, 0x0f, 0xff, 0xff, 0x80, 0x0f, 0xff, 0xfe, 0x00, 0x0f, 34 | 0xff, 0xf8, 0x00, 0x0f, 0xff, 0xe0, 0x00, 0x0f, 0xff, 0x80, 0x00, 0x3f, 0xfe, 0x00, 0x01, 0xff, 35 | 0xfc, 0x00, 0x03, 0xff, 0xe6, 0x00, 0x00, 0xff, 0x81, 0xc0, 0x00, 0x3f, 0x00, 0x70, 0x00, 0x0f, 36 | 0x00, 0x1c, 0x00, 0x0f, 0x00, 0x03, 0x00, 0x0f, 0xc0, 0x00, 0xc0, 0x0f, 0xf0, 0x00, 0x30, 0x0f, 37 | 0xfc, 0x00, 0x0c, 0x0f, 0xff, 0x00, 0x03, 0x0f, 0xff, 0xc0, 0x00, 0xef, 0xff, 0xf0, 0x00, 0x3f, 38 | 0xff, 0xfc, 0x00, 0x0f, 0xff, 0xff, 0x80, 0x0f, 0xff, 0xff, 0xe0, 0x0f, 0xff, 0xff, 0xf8, 0x0f, 39 | 0xff, 0xff, 0xfe, 0x0f, 0xff, 0xff, 0xff, 0x8f, 0xff, 0xff, 0xff, 0xef, 0xff, 0xff, 0xff, 0xff, 40 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 41 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 42 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 43 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 44 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff 45 | }; 46 | 47 | const unsigned char bmp_tanoc [] PROGMEM = { 48 | // 'tanoc', 32x160px 49 | 0xff, 0xe0, 0x0f, 0xff, 0xff, 0x80, 0x03, 0xff, 0xff, 0x0f, 0xe0, 0xff, 0xfe, 0x3f, 0xf8, 0x7f, 50 | 0xfc, 0xff, 0xfe, 0x3f, 0xf9, 0xef, 0xff, 0x1f, 0xf1, 0xe7, 0xff, 0x9f, 0xf3, 0xc7, 0xff, 0x8f, 51 | 0xe7, 0xc7, 0xff, 0xcf, 0xe7, 0xff, 0xff, 0xcf, 0xe7, 0xe0, 0x01, 0xe7, 0xe7, 0xe7, 0xf9, 0xe7, 52 | 0xef, 0xef, 0xf9, 0xe7, 0xef, 0xef, 0xf9, 0xe7, 0xff, 0xef, 0xf9, 0xe7, 0xff, 0xe0, 0x01, 0xe7, 53 | 0xff, 0xf8, 0x01, 0xc7, 0xff, 0xef, 0xff, 0xcf, 0xff, 0xe7, 0xff, 0xcf, 0xff, 0x87, 0xff, 0x9f, 54 | 0xff, 0xc7, 0xff, 0x1f, 0xff, 0xff, 0xfe, 0x3f, 0xff, 0xff, 0xfc, 0x7f, 0xff, 0xff, 0xf8, 0xff, 55 | 0xff, 0xff, 0xc1, 0xff, 0xff, 0xfe, 0x07, 0xff, 0xff, 0xfe, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 56 | 0xff, 0xff, 0xff, 0xff, 0xfc, 0x1b, 0xff, 0xff, 0xfc, 0x38, 0xff, 0xff, 0xff, 0x78, 0x7f, 0xff, 57 | 0xff, 0x78, 0x7f, 0xff, 0xff, 0x78, 0x7f, 0xff, 0xfc, 0x3c, 0x7f, 0xff, 0xfc, 0x1c, 0x7f, 0xff, 58 | 0xff, 0xfc, 0x7f, 0xff, 0xff, 0xfc, 0x7f, 0xff, 0xff, 0xfc, 0x7f, 0xff, 0xff, 0xfc, 0x7f, 0xff, 59 | 0xff, 0xfc, 0x7f, 0xff, 0xff, 0xfc, 0x7f, 0xff, 0xff, 0xfc, 0x7f, 0xff, 0xff, 0xfc, 0x00, 0x3f, 60 | 0xff, 0xfc, 0x00, 0x3f, 0xff, 0xfc, 0x00, 0x3f, 0xff, 0xfc, 0x7f, 0xff, 0xff, 0x9c, 0x7f, 0xff, 61 | 0xff, 0x1c, 0x7f, 0xff, 0xfc, 0x3c, 0x7f, 0xff, 0xfc, 0xbc, 0x7f, 0xff, 0xfc, 0x3c, 0x7f, 0xff, 62 | 0xfe, 0x3e, 0x7f, 0xff, 0xff, 0x1f, 0xff, 0xff, 0xff, 0xdc, 0x00, 0x3f, 0xff, 0xfc, 0x00, 0x3f, 63 | 0xff, 0xfc, 0x71, 0xff, 0xff, 0xfc, 0x71, 0xff, 0xff, 0xfc, 0x71, 0xff, 0xff, 0xfc, 0x31, 0xff, 64 | 0xff, 0xfe, 0x31, 0xff, 0xff, 0xff, 0x11, 0xff, 0xff, 0xff, 0x19, 0xff, 0xff, 0xff, 0x89, 0xff, 65 | 0xff, 0xff, 0x87, 0xff, 0xfc, 0x1f, 0xc3, 0xff, 0xfc, 0x3f, 0xe3, 0xff, 0xfd, 0x7f, 0xf1, 0xff, 66 | 0xfd, 0x3f, 0xf0, 0xff, 0xfc, 0x1f, 0xf8, 0xff, 0xfc, 0x1f, 0xfc, 0x7f, 0xfc, 0xdf, 0xfc, 0x3f, 67 | 0xff, 0xff, 0xfe, 0x3f, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xff, 0xff, 0xbf, 68 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x7f, 0xff, 0xfc, 0x00, 0x3f, 0xff, 0xfc, 0x00, 0x3f, 69 | 0xff, 0xfc, 0x7f, 0xff, 0xff, 0xfc, 0x7f, 0xff, 0xfc, 0x1c, 0x7f, 0xff, 0xfc, 0x1c, 0x3f, 0xff, 70 | 0xfd, 0xde, 0x1f, 0xff, 0xfd, 0xdf, 0x1f, 0xff, 0xfd, 0x9f, 0x0f, 0xff, 0xfc, 0x9f, 0x87, 0xff, 71 | 0xfc, 0x1f, 0xc7, 0xff, 0xfe, 0x3f, 0xe3, 0xff, 0xff, 0xff, 0xe1, 0xff, 0xff, 0xff, 0xf0, 0xff, 72 | 0xff, 0xff, 0xf8, 0x7f, 0xff, 0xff, 0xfc, 0x7f, 0xff, 0xff, 0xfc, 0x3f, 0xff, 0xff, 0xfe, 0x3f, 73 | 0xff, 0xff, 0xfe, 0x3f, 0xff, 0xff, 0xfe, 0x3f, 0xff, 0xfe, 0x00, 0x3f, 0xff, 0xfc, 0x00, 0x3f, 74 | 0xfc, 0x1e, 0x00, 0x3f, 0xfc, 0x1f, 0xff, 0xff, 0xfd, 0xde, 0x00, 0x3f, 0xfd, 0xdc, 0x00, 0x3f, 75 | 0xfd, 0xdc, 0x00, 0x3f, 0xfc, 0x9c, 0x63, 0x3f, 0xfc, 0x9c, 0x42, 0x3f, 0xff, 0xfc, 0x42, 0x3f, 76 | 0xff, 0xfc, 0x42, 0x3f, 0xff, 0xfc, 0x42, 0x3f, 0xff, 0xfc, 0x42, 0x3f, 0xff, 0xfc, 0x42, 0x3f, 77 | 0xff, 0xfc, 0x42, 0x3f, 0xff, 0xfc, 0x42, 0x3f, 0xff, 0xfc, 0x42, 0x3f, 0xff, 0xfc, 0x42, 0x3f, 78 | 0xff, 0xfc, 0x42, 0x3f, 0xfc, 0x3c, 0x42, 0x3f, 0xfc, 0x1c, 0x42, 0x3f, 0xfd, 0x9c, 0x42, 0x3f, 79 | 0xfd, 0xdc, 0x42, 0x3f, 0xfd, 0xdc, 0x63, 0x3f, 0xfd, 0xdc, 0x00, 0x3f, 0xfc, 0x1c, 0x00, 0x3f, 80 | 0xfc, 0x1e, 0x00, 0x3f, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xf0, 0x3f, 0xff, 0xff, 0xe0, 0x1f, 0xff, 81 | 0xff, 0xe3, 0x1f, 0xff, 0xff, 0xe3, 0x1f, 0xff, 0xff, 0xe7, 0x1f, 0xff, 0xff, 0xe0, 0x1f, 0xff, 82 | 0xff, 0xf0, 0x3f, 0xff, 0xff, 0xf8, 0x7f, 0xff, 0xff, 0xff, 0x80, 0x3f, 0xff, 0xfc, 0x00, 0x3f, 83 | 0xfc, 0x1c, 0x00, 0x3f, 0xfc, 0x1c, 0x7e, 0x3f, 0xfd, 0x7c, 0x7e, 0x3f, 0xfd, 0x3c, 0x7e, 0x3f, 84 | 0xfd, 0x1c, 0x7e, 0x3f, 0xfc, 0x1c, 0x7e, 0x3f, 0xfc, 0x5c, 0x7e, 0x3f, 0xff, 0xfc, 0x7e, 0x3f, 85 | 0xff, 0xfc, 0x7e, 0x3f, 0xff, 0xfc, 0x7e, 0x3f, 0xff, 0xfc, 0x7e, 0x3f, 0xff, 0xfc, 0x7f, 0xff, 86 | 0xff, 0xfc, 0x7f, 0xff, 0xff, 0xfc, 0x7f, 0xff, 0xff, 0xfc, 0x7f, 0xff, 0xff, 0xfc, 0x7f, 0xff, 87 | 0xff, 0xfc, 0x7f, 0xff, 0xfc, 0x1c, 0x7f, 0xff, 0xfc, 0x1c, 0x7f, 0xff, 0xfd, 0x58, 0x7f, 0xff, 88 | 0xfd, 0x58, 0x7f, 0xff, 0xfd, 0x58, 0xff, 0xff, 0xfd, 0xdb, 0xff, 0xff, 0xfd, 0xdf, 0xff, 0xff 89 | }; 90 | 91 | img_data_t IMG_WOCAO = { 92 | true, 93 | bmp_wocao, 94 | 160, 95 | 4, // 32pix div 8bit 96 | true 97 | }; 98 | 99 | img_data_t IMG_TANOC = { 100 | true, 101 | bmp_tanoc, 102 | 160, 103 | 4, // 32pix div 8bit 104 | true 105 | }; 106 | 107 | size_t img_offset_at(img_data_t * image, uint16_t left, uint8_t top) { 108 | return left * image->height_strides + top; 109 | } 110 | 111 | img_data_t img_invert(img_data_t * image) { 112 | img_data_t new_data = { 113 | image->is_progmem, 114 | image->data, 115 | image->width_pixels, 116 | image->height_strides, 117 | !image->inverse 118 | }; 119 | return new_data; 120 | } 121 | --------------------------------------------------------------------------------