├── book ├── .gitignore ├── book.toml └── src │ ├── dev_manual.md │ ├── dev │ ├── software.md │ ├── dependency_injection.md │ ├── hardware.md │ └── develop_environment.md │ ├── feature_counter_reset.md │ ├── SUMMARY.md │ ├── installation.md │ ├── port_04_mini_etc.md │ ├── application.md │ ├── feature_disp_hw_info.md │ ├── overview.md │ ├── port_overview.md │ ├── port_etc.md │ ├── feature_disp_rom.md │ ├── port_04_mini_overview.md │ ├── port_04_mini_vend_side.md │ ├── port_04_overview.md │ ├── port_04_mini_host_side.md │ ├── dip_switch.md │ ├── port_vend_side.md │ └── port_host_side.md ├── rustfmt.toml ├── src ├── components │ ├── .gitignore │ ├── mod.rs │ ├── dip_switch.rs │ ├── start_button.rs │ ├── vend_side_bill.rs │ └── host_side_bill.rs ├── types │ ├── mod.rs │ ├── fault_log.rs │ ├── player.rs │ ├── const_convert.rs │ ├── buffered_opendrain_kind.rs │ └── input_port.rs ├── semi_layer │ ├── mod.rs │ ├── timing.rs │ ├── buffered_wait_receiver.rs │ └── buffered_wait.rs ├── mp_fingerprint │ └── mod.rs ├── application │ ├── player_to_vend_led.rs │ ├── io_bypass.rs │ ├── io_card.rs │ ├── pulse_meory_filter.rs │ ├── io_remap.rs │ └── mutual_inhibit.rs ├── boards │ ├── const_str.rs │ ├── billmock_0v3.rs │ ├── billmock_0v2.rs │ ├── billmock_0v4.rs │ ├── billmock_mini_0v4.rs │ └── billmock_mini_0v5.rs └── main.rs ├── .vscode ├── .gitignore ├── task.json ├── extensions.json └── settings.json ├── .gitignore ├── .reuse └── templates │ └── rust.commented.jinja2 ├── docs ├── dependencies.md └── SerialDevice.md ├── billmock-plug-card ├── src │ ├── common.rs │ └── lib.rs └── Cargo.toml ├── card-terminal-adapter ├── Cargo.toml └── src │ ├── lib.rs │ └── types.rs ├── rust-toolchain.toml ├── memory.x ├── LICENSES ├── MIT.txt ├── CC0-1.0.txt └── Apache-2.0.txt ├── .github └── workflows │ └── gh-pages.yml ├── .pre-commit-config.yaml ├── .cargo └── config.toml ├── Cargo.toml └── README.md /book/.gitignore: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: © 2023 Jinwoo Park (pmnxis@gmail.com) 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | 5 | book 6 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: © 2023 Jinwoo Park (pmnxis@gmail.com) 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | 5 | group_imports = "StdExternalCrate" 6 | # max_width=120 -------------------------------------------------------------------------------- /src/components/.gitignore: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: © 2023 Jinwoo Park (pmnxis@gmail.com) 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | 5 | backup_notuse 6 | backup_notuse/** 7 | nda 8 | -------------------------------------------------------------------------------- /.vscode/.gitignore: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: © 2023 Jinwoo Park (pmnxis@gmail.com) 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | 5 | *.cortex-debug.*.json 6 | launch.json 7 | tasks.json 8 | *.cfg 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: © 2023 Jinwoo Park (pmnxis@gmail.com) 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | 5 | target 6 | target_ci 7 | target_ci_stable 8 | Cargo.lock 9 | third_party 10 | out/ 11 | nda 12 | nda_note.md 13 | *.svd 14 | .vscode/launch.json 15 | -------------------------------------------------------------------------------- /.reuse/templates/rust.commented.jinja2: -------------------------------------------------------------------------------- 1 | /* 2 | {% for copyright_line in copyright_lines %} 3 | * {{ copyright_line }} 4 | {% endfor %} 5 | {% if copyright_lines and spdx_expressions %} 6 | * 7 | {% endif %} 8 | {% for expression in spdx_expressions %} 9 | * SPDX-License-Identifier: {{ expression }} 10 | {% endfor %} 11 | */ 12 | -------------------------------------------------------------------------------- /src/types/mod.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © 2023 Jinwoo Park (pmnxis@gmail.com) 3 | * 4 | * SPDX-License-Identifier: MIT OR Apache-2.0 5 | */ 6 | 7 | pub mod dip_switch_config; 8 | 9 | pub mod input_port; 10 | 11 | pub mod const_convert; 12 | 13 | pub mod player; 14 | 15 | pub mod buffered_opendrain_kind; 16 | 17 | pub mod fault_log; 18 | -------------------------------------------------------------------------------- /src/semi_layer/mod.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © 2023 Jinwoo Park (pmnxis@gmail.com) 3 | * 4 | * SPDX-License-Identifier: MIT OR Apache-2.0 5 | */ 6 | 7 | // [`semi-layer`] justify common necessary 8 | 9 | pub(crate) mod buffered_opendrain; 10 | pub(crate) mod buffered_wait; 11 | pub(crate) mod buffered_wait_receiver; 12 | 13 | pub mod timing; 14 | -------------------------------------------------------------------------------- /src/components/mod.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © 2023 Jinwoo Park (pmnxis@gmail.com) 3 | * 4 | * SPDX-License-Identifier: MIT OR Apache-2.0 5 | */ 6 | 7 | pub(crate) mod dip_switch; 8 | pub(crate) mod host_side_bill; 9 | pub(crate) mod start_button; 10 | pub(crate) mod vend_side_bill; 11 | 12 | pub(crate) mod serial_device; 13 | 14 | pub(crate) mod eeprom; 15 | -------------------------------------------------------------------------------- /book/book.toml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: © 2023 Jinwoo Park (pmnxis@gmail.com) 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | 5 | [book] 6 | authors = ["pmnxis(Jinwoo Park)"] 7 | language = "en" 8 | multilingual = true 9 | src = "src" 10 | title = "BillMock" 11 | description = "BillMock SW/HW manual" 12 | 13 | [output.html] 14 | site-url = "/billmock-app-rs" 15 | git-repository-url = "https://github.com/pmnxis/billmock-app-rs" 16 | -------------------------------------------------------------------------------- /docs/dependencies.md: -------------------------------------------------------------------------------- 1 | 6 | # Dependencies 7 | 8 | ### Minimum installation 9 | ``` 10 | cargo install cargo-binutils 11 | rustup component add llvm-tools-preview 12 | cargo install probe-rs-cli 13 | ``` 14 | 15 | ### Linux USB Issue 16 | see : https://embedded-trainings.ferrous-systems.com/installation.html#linux-only-usb -------------------------------------------------------------------------------- /billmock-plug-card/src/common.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © 2023 Jinwoo Park (pmnxis@gmail.com) 3 | * 4 | * SPDX-License-Identifier: MIT OR Apache-2.0 5 | */ 6 | 7 | pub(crate) const KICC_STX: u8 = 0x02; 8 | pub(crate) const KICC_ACK: u8 = 0x06; 9 | pub(crate) const KICC_NACK: u8 = 0x15; 10 | pub(crate) const KICC_ETX: u8 = 0x03; 11 | 12 | pub(crate) const RAW_DATA_ACK: [u8; 3] = [KICC_ACK, KICC_ACK, KICC_ACK]; 13 | pub(crate) const RAW_DATA_NACK: [u8; 3] = [KICC_NACK, KICC_NACK, KICC_NACK]; 14 | -------------------------------------------------------------------------------- /.vscode/task.json: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © 2023 Jinwoo Park (pmnxis@gmail.com) 3 | * 4 | * SPDX-License-Identifier: CC0-1.0 5 | */ 6 | { 7 | "version": "2.0.0", 8 | "tasks": [ 9 | { 10 | "label": "cargo build", 11 | "type": "shell", 12 | "command": "~/.cargo/bin/cargo", 13 | "args": [ 14 | "build", 15 | "--release" 16 | ], 17 | "group": { 18 | "kind": "build", 19 | "isDefault": true 20 | } 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /card-terminal-adapter/Cargo.toml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: © 2023 Jinwoo Park (pmnxis@gmail.com) 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | 5 | [package] 6 | name = "card-terminal-adapter" 7 | version = "0.1.0" 8 | edition = "2021" 9 | authors = ["Jinwoo Park "] 10 | license = "MIT OR Apache-2.0" 11 | description = "Card terminal interface adapte for billmock-app-rs" 12 | 13 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 14 | 15 | [dependencies] 16 | static_assertions = "1.1.0" 17 | defmt = "0.3" 18 | zeroable = "0.2.0" 19 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: © 2023 Jinwoo Park (pmnxis@gmail.com) 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | 5 | # Before upgrading check that everything is available on all tier1 targets here: 6 | # https://rust-lang.github.io/rustup-components-history 7 | [toolchain] 8 | # channel = "nightly-2023-11-01" 9 | # Since Nightly 2024-06-13 make_static doesn't work, https://github.com/embassy-rs/static-cell/issues/16 10 | channel = "nightly-2024-06-12" 11 | components = [ "rust-src", "rustfmt", "llvm-tools", "rust-analyzer" ] 12 | targets = [ 13 | "thumbv6m-none-eabi", 14 | ] 15 | -------------------------------------------------------------------------------- /memory.x: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © 2023 Jinwoo Park (pmnxis@gmail.com) 3 | * 4 | * SPDX-License-Identifier: MIT OR Apache-2.0 5 | */ 6 | 7 | /* STM32G030C8 */ 8 | MEMORY 9 | { 10 | FLASH : ORIGIN = 0x08000000, LENGTH = 64K 11 | RAM : ORIGIN = 0x20000000, LENGTH = 8K 12 | } 13 | 14 | /* 15 | * Mass-Production usage ELF section 16 | * ref - https://github.com/pmnxis/billmock-app-rs/issues/40 17 | * ref - https://sourceware.org/binutils/docs/ld/Output-Section-Type.html 18 | */ 19 | SECTIONS { 20 | .mp_fingerprint 0 (OVERLAY) : 21 | { 22 | KEEP(*(.mp_fingerprint)) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/types/fault_log.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © 2023 Jinwoo Park (pmnxis@gmail.com) 3 | * 4 | * SPDX-License-Identifier: MIT OR Apache-2.0 5 | */ 6 | 7 | use static_assertions::*; 8 | use zeroable::Zeroable; 9 | 10 | #[repr(C, packed(2))] 11 | #[derive(Clone)] 12 | pub struct FaultLog { 13 | pub current_boot_cnt: u32, 14 | pub error_code: u16, 15 | } 16 | assert_eq_size!(FaultLog, [u8; 6]); 17 | 18 | unsafe impl Zeroable for FaultLog { 19 | fn zeroed() -> Self { 20 | Self { 21 | current_boot_cnt: 0, 22 | error_code: 0, 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /book/src/dev_manual.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | # Developer Manual 8 | 9 | # Write In Rust 10 | Used rust experimentally. 11 | 12 | This repository is aiming three goal. 13 | One for development of production firmware and second is making a proof of concept that rust embedded is usable for actual embedded production. And last goal is setting some example about production-rust-embedded-code. 14 | 15 | The software code is in [billmock-app-rs](https://github.com/pmnxis/billmock-app-rs) , except NDA related code. 16 | -------------------------------------------------------------------------------- /book/src/dev/software.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | # Software 8 | 9 | ## Card Terminal Connectivity in actual environment 10 | The firmware being developed is based on KICC's ED-785 terminal for mass production. However, in accordance with the NDA agreement, the code related to this is not present in the public codebase. Instead, it is managed separately through dependency injection. Therefore, the publicly available source code contains only example protocol code that does not actually perform any real operations. 11 | -------------------------------------------------------------------------------- /docs/SerialDevice.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | # Serial Device 8 | Serial Device component and this directory implmentation for serial device that send bill signal via RS232. 9 | This component writed up for virtual serial type serial device that not existing in actual marketplace. 10 | 11 | ## Limitation of this component 12 | Actual product code(by GPARK) for this component uses some specific actual device under the confidential and NDA. 13 | So this code space does not compatible with actual commercial usage in South Korea. 14 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © 2023 Jinwoo Park (pmnxis@gmail.com) 3 | * 4 | * SPDX-License-Identifier: CC0-1.0 5 | */ 6 | { 7 | // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. 8 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp 9 | // List of extensions which should be recommended for users of this workspace. 10 | "recommendations": [ 11 | "rust-lang.rust-analyzer", 12 | "tamasfe.even-better-toml", 13 | ], 14 | // List of extensions recommended by VS Code that should not be recommended for users of this workspace. 15 | "unwantedRecommendations": [] 16 | } -------------------------------------------------------------------------------- /billmock-plug-card/Cargo.toml: -------------------------------------------------------------------------------- 1 | 2 | 3 | # SPDX-FileCopyrightText: © 2023 Jinwoo Park (pmnxis@gmail.com) 4 | # 5 | # SPDX-License-Identifier: CC0-1.0 6 | 7 | [package] 8 | name = "billmock-plug-card" 9 | version = "0.1.0" 10 | edition = "2021" 11 | authors = ["Jinwoo Park "] 12 | license = "MIT OR Apache-2.0" 13 | description = "Example implementation of serial arcade payment method for billmock-app-rs" 14 | 15 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 16 | 17 | [dependencies] 18 | card-terminal-adapter = { path = "../card-terminal-adapter" } # Import generic interface for billmock-app-rs 19 | 20 | defmt = "0.3" 21 | defmt-test = "0.3.0" 22 | crc = "3.2.1" 23 | 24 | [dev-dependencies] 25 | log = { version = "0.4", features = ["max_level_debug", "release_max_level_warn"] } 26 | -------------------------------------------------------------------------------- /src/types/player.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © 2023 Jinwoo Park (pmnxis@gmail.com) 3 | * 4 | * SPDX-License-Identifier: MIT OR Apache-2.0 5 | */ 6 | 7 | use num_enum::{IntoPrimitive, TryFromPrimitive}; 8 | 9 | use crate::types::const_convert::ConstInto; 10 | 11 | #[allow(dead_code)] 12 | #[repr(u8)] 13 | #[derive( 14 | Debug, 15 | defmt::Format, 16 | Clone, 17 | Copy, 18 | Eq, 19 | PartialEq, 20 | Ord, 21 | PartialOrd, 22 | TryFromPrimitive, 23 | IntoPrimitive, 24 | )] 25 | pub enum Player { 26 | Undefined = 0, 27 | Player1 = 1, 28 | Player2 = 2, 29 | } 30 | 31 | impl Player { 32 | pub const fn default() -> Self { 33 | Self::Undefined 34 | } 35 | } 36 | 37 | impl ConstInto for Player { 38 | fn const_into(self) -> usize { 39 | self as usize 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © 2023 Jinwoo Park (pmnxis@gmail.com) 3 | * 4 | * SPDX-License-Identifier: CC0-1.0 5 | */ 6 | { 7 | "editor.formatOnSave": true, 8 | "[toml]": { 9 | "editor.formatOnSave": false 10 | }, 11 | "rust-analyzer.check.allTargets": false, 12 | // "rust-analyzer.check.noDefaultFeatures": true, 13 | // "rust-analyzer.cargo.noDefaultFeatures": true, 14 | "rust-analyzer.showUnlinkedFileNotification": false, 15 | // "rust-analyzer.check.features": [ 16 | // "billmock_default", 17 | // "hw_0v3" 18 | // ], 19 | // "rust-analyzer.cargo.features": "all", 20 | "rust-analyzer.checkOnSave.extraArgs": [ 21 | "--bins" 22 | ], 23 | "rust-analyzer.cargo.target": "thumbv6m-none-eabi", 24 | "rust-analyzer.linkedProjects": [ 25 | "Cargo.toml", 26 | "billmock-plug-card/Cargo.toml", 27 | "card-terminal-adapter/Cargo.toml" 28 | ], 29 | } -------------------------------------------------------------------------------- /src/mp_fingerprint/mod.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © 2023 Jinwoo Park (pmnxis@gmail.com) 3 | * 4 | * SPDX-License-Identifier: MIT OR Apache-2.0 5 | */ 6 | 7 | //! This code is not reflected on program binary or actual memory. 8 | //! .mp_fingerprint section is `NOLOAD` and `READONLY` section that 9 | //! virtually existed in ELF header. 10 | //! The contents of .mp_fingerprint would be used for billmock-mptool 11 | //! to determine what kind of board, feature, version, git hash based 12 | //! from ELF binary. 13 | 14 | //! DO NOT USE `&str` or any other slice type 15 | //! ```rs 16 | //! #[allow(unused, dead_code)] 17 | //! #[no_mangle] 18 | //! #[link_section = ".mp_fingerprint"] 19 | //! static TEST_FINGER: [u8; 14] = *b"SOME TOML HERE"; 20 | //! ``` 21 | 22 | use env_to_array::patch_linker_section_from_hex_env; 23 | 24 | patch_linker_section_from_hex_env!(".mp_fingerprint", "MP_INFO_TOML", "MP_FINGERPRINT_TOML_HEX"); 25 | -------------------------------------------------------------------------------- /book/src/feature_counter_reset.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | # Counter Reset 8 | 9 | ![display counter reset screen](https://billmock.gpark.biz/images/dip_switch_disp_reset_rom_enus.png) 10 | 11 | - The warning message appears for 10 seconds, and the ROM contents are already initialized when the warning message is displayed. 12 | 13 | - The counts displayed in [DispRom](./feature_disp_rom.md) for `P1 Card`, `P2 Card`, `P1 Coin`, `P2 Coin` are reset to 0, but information such as boot count and uptime remains unaffected. 14 | 15 | - This feature is available starting from firmware version `0.3.1` and hardware version `0.5` or `Mini 0.5`. The SVC button on hardware version `0.5` or `Mini 0.5` must be held for more than 10 seconds to activate this feature. 16 | > ![svc button](https://billmock.gpark.biz/images/svc_button.jpg) 17 | -------------------------------------------------------------------------------- /book/src/dev/dependency_injection.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | # Dependency Injection for card reader 8 | To build with NDA features (GPARK Limited or own secret dependency), need adding following command on each `cargo` command. 9 | build, run or any other `cargo` command. 10 | 11 | ```sh 12 | # dependency injection from git repository 13 | # CAUTION , this should be work but not working 14 | --config "patch.'https://github.com/pmnxis/billmock-app-rs.git'.billmock-plug-card.git = \"https://github.com/user_name/repo_name.git\"" 15 | 16 | # dependency injection from local repository 17 | # this works 18 | --config "patch.'https://github.com/pmnxis/billmock-app-rs.git'.billmock-plug-card.path = \"../repo_name\"" 19 | ``` 20 | 21 | In this repository, experimentally utilize dependency injection that the 'patch' function of 'cargo' to coexist both NDA code and open source example code. 22 | -------------------------------------------------------------------------------- /src/application/player_to_vend_led.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © 2023 Jinwoo Park (pmnxis@gmail.com) 3 | * 4 | * SPDX-License-Identifier: MIT OR Apache-2.0 5 | */ 6 | 7 | use crate::boards::{Board, LED_1_INDEX, LED_2_INDEX, PLAYER_1_INDEX, PLAYER_2_INDEX}; 8 | use crate::semi_layer::buffered_opendrain::BufferedOpenDrain; 9 | use crate::types::player::Player; 10 | 11 | impl Player { 12 | pub const fn to_vend_busy_led( 13 | self, 14 | board: &'static Board, 15 | ) -> (&BufferedOpenDrain, &BufferedOpenDrain, &BufferedOpenDrain) { 16 | match self { 17 | Player::Player2 => ( 18 | &board.hardware.host_sides[PLAYER_2_INDEX].out_vend, 19 | &board.hardware.host_sides[PLAYER_2_INDEX].out_busy, 20 | &board.hardware.indicators[LED_2_INDEX], 21 | ), 22 | _ => ( 23 | &board.hardware.host_sides[PLAYER_1_INDEX].out_vend, 24 | &board.hardware.host_sides[PLAYER_2_INDEX].out_busy, 25 | &board.hardware.indicators[LED_1_INDEX], 26 | ), 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /LICENSES/MIT.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Jinwoo Park and other billmock-app-rs contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/boards/const_str.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © 2023 Jinwoo Park (pmnxis@gmail.com) 3 | * 4 | * SPDX-License-Identifier: MIT OR Apache-2.0 5 | */ 6 | 7 | use billmock_otp_dev_info::OtpDeviceInfo; 8 | use env_to_array::hex_env_to_array; 9 | 10 | pub const PROJECT_NAME: &str = env!("PROJECT_NAME"); 11 | 12 | pub const VERSION_STR: [u8; card_terminal_adapter::FW_VER_LEN] = 13 | hex_env_to_array!("PROJECT_VERSION"); 14 | 15 | pub const COMMIT_HASH: &str = env!("GIT_COMMIT_HASH"); 16 | 17 | pub(crate) const COMMIT_SHORT: [u8; card_terminal_adapter::GIT_HASH_LEN] = 18 | hex_env_to_array!("GIT_COMMIT_SHORT_HASH"); 19 | 20 | pub const SERIAL_NUMBER_WHEN_UNKNOWN: [u8; card_terminal_adapter::DEV_SN_LEN] = *b" unknown"; 21 | 22 | pub const GIT_COMMIT_DATETIME: &str = env!("GIT_COMMIT_DATETIME"); 23 | pub const PRINT_BAR: &str = "+-----------------------------------------------------------+"; 24 | 25 | pub fn get_serial_number() -> &'static [u8; card_terminal_adapter::DEV_SN_LEN] { 26 | let otp_space = OtpDeviceInfo::from_stm32g0(); 27 | 28 | match otp_space.check_and_sn() { 29 | Err(_) => &SERIAL_NUMBER_WHEN_UNKNOWN, 30 | Ok(_) => &otp_space.dev_sn, 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /book/src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | # Summary 8 | 9 | - [Overview](./overview.md) 10 | - [DIP Switch Configuration](./dip_switch.md) 11 | - [Application Logic](./application.md) 12 | - [`DisplayRom` feature](./feature_disp_rom.md) 13 | - [`DisplayHwInfo` feature](./feature_disp_hw_info.md) 14 | - [`Counter Reset` feature](./feature_counter_reset.md) 15 | - [Machine installation](./installation.md) 16 | - [Hardware and pin-map](./port_overview.md) 17 | - [BillMock Mini (Rectangular)](./port_04_mini_overview.md) 18 | - [Vend side (Top)](./port_04_mini_vend_side.md) 19 | - [Host Side (Bottom)](./port_04_mini_host_side.md) 20 | - [Miscellaneous](./port_04_mini_etc.md) 21 | - [BillMock (Square)](./port_04_overview.md) 22 | - [Vend side (Top)](./port_vend_side.md) 23 | - [Host Side (Bottom)](./port_host_side.md) 24 | - [Miscellaneous](./port_etc.md) 25 | - [Developer Manual 🛠️⚠️](./dev_manual.md) 26 | - [Software 👨🏽‍💻](./dev/software.md) 27 | - [Develop Environment](./dev/develop_environment.md) 28 | - [Dependency Injection](./dev/dependency_injection.md) 29 | - [Hardware 🔩](./dev/hardware.md) 30 | -------------------------------------------------------------------------------- /.github/workflows/gh-pages.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: © 2023 Jinwoo Park (pmnxis@gmail.com) 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | 5 | name: github pages 6 | 7 | on: 8 | # Runs on pushes targeting the default branch 9 | push: 10 | branches: ["master"] 11 | 12 | permissions: 13 | contents: read 14 | pages: write 15 | id-token: write 16 | 17 | jobs: 18 | # Build job 19 | build: 20 | runs-on: ubuntu-20.04 21 | # env: 22 | # MDBOOK_VERSION: 0.4.21 23 | steps: 24 | - uses: actions/checkout@v3 25 | - name: Setup mdBook 26 | uses: peaceiris/actions-mdbook@v1 27 | with: 28 | mdbook-version: 'latest' 29 | - name: Setup Pages 30 | id: pages 31 | uses: actions/configure-pages@v3 32 | - name: Build with mdBook 33 | run: mdbook build book 34 | - name: Upload artifact 35 | uses: actions/upload-pages-artifact@v2 36 | with: 37 | path: ./book/book 38 | 39 | # Deployment job 40 | deploy: 41 | environment: 42 | name: github-pages 43 | url: ${{ steps.deployment.outputs.page_url }} 44 | runs-on: ubuntu-latest 45 | needs: build 46 | steps: 47 | - name: Deploy to GitHub Pages 48 | id: deployment 49 | uses: actions/deploy-pages@v2 50 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: © 2023 Jinwoo Park (pmnxis@gmail.com) 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | 5 | # See https://pre-commit.com for more information 6 | # See https://pre-commit.com/hooks.html for more hooks 7 | repos: 8 | - repo: https://github.com/pre-commit/pre-commit-hooks 9 | rev: v3.2.0 10 | hooks: 11 | - id: trailing-whitespace 12 | - id: end-of-file-fixer 13 | - id: check-added-large-files 14 | - repo: local 15 | hooks: 16 | - id: rust-linting 17 | name: Rust linting 18 | description: Run cargo fmt on files included in the commit. rustfmt should be installed before-hand. 19 | entry: cargo fmt --all -- 20 | pass_filenames: true 21 | types: [file, rust] 22 | language: system 23 | - id: rust-clippy 24 | name: Rust clippy 25 | description: Run cargo clippy on files included in the commit. clippy should be installed before-hand. 26 | entry: cargo clippy --all-targets --all-features -- -Dclippy::all 27 | pass_filenames: false 28 | types: [file, rust] 29 | language: system 30 | - id: fsfe-reuse 31 | name: FSFE REUSE 32 | description: Run FSFE REUSE lint on files included in the commit. reuse should be installed before-hand. 33 | entry: reuse lint 34 | pass_filenames: false 35 | language: system 36 | -------------------------------------------------------------------------------- /book/src/installation.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | # Machine installation 8 | When connecting to actual arcade machines, there are two installation options available, each with its own advantages and disadvantages. It is recommended to test both methods and select the one that best suits your needs. 9 | 10 | ## MOLEX Harness 11 | 12 | 13 | 1. Prepare the Harness Based on the Pinout Chart 14 | 2. Connect 15 | 16 | ## Cut previous harness and connect to terminal 17 | 18 | 19 | 20 | 1. Assuming there is some flexibility in the cable length, cut the cable at the installation point. 21 | 2. Strip the insulation from both ends of the cut cable, leaving approximately 1cm (10mm) of exposed wire. 22 | 3. While pressing the orange latch on the top/bottom terminal, securely insert the stripped wire into the hole. 23 | 24 | - Note: Typically, the top side should be connected to the actual currency payment device, while the bottom side should be connected to the GAME I/O PCB. 25 | - Note: Ensure there is some length flexibility in the existing cables. 26 | - Note: Only cables conforming to UL/CUL standards with AWG20 or higher and AWG26 or lower can be used with these terminals. 27 | -------------------------------------------------------------------------------- /src/types/const_convert.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © 2023 Jinwoo Park (pmnxis@gmail.com) 3 | * 4 | * SPDX-License-Identifier: MIT OR Apache-2.0 5 | */ 6 | 7 | //! Experimental const-ish From/Into traits for firmware eco-system 8 | //! Following codes are referenced to rust core's convert codes. 9 | 10 | /// Compile-time [`From`] type for const boundary 11 | #[const_trait] 12 | pub trait ConstFrom: Sized { 13 | /// Converts to this type from the input type. 14 | fn const_from(value: T) -> Self; 15 | } 16 | 17 | /// Compile-time [`Into`] type for const boundary 18 | #[const_trait] 19 | pub trait ConstInto: Sized { 20 | /// Converts this type into the (usually inferred) input type. 21 | #[must_use] 22 | fn const_into(self) -> T; 23 | } 24 | 25 | impl const ConstInto for T 26 | where 27 | U: ~const ConstFrom + ~const ConstInto, 28 | { 29 | /// Calls `U::const_from(self)`. 30 | /// 31 | /// That is, this conversion is whatever the implementation of 32 | /// [ConstFrom]<T> for U chooses to do. 33 | #[inline] 34 | #[track_caller] 35 | fn const_into(self) -> U { 36 | U::const_from(self) 37 | } 38 | } 39 | 40 | // ConstFrom (and thus ConstInto) is reflexive 41 | impl const ConstFrom for T { 42 | /// Returns the argument unchanged. 43 | #[inline(always)] 44 | fn const_from(t: T) -> T { 45 | t 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /book/src/port_04_mini_etc.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | # Port - Miscellaneous 8 | 9 | ## Credit Card Reader Port 10 | 11 | ![J1](https://billmock.gpark.biz/images/pcb_0v4_mini_port/J1.png) 12 | 13 | 14 | 15 | 40 | 50 |
16 | 17 | | | 18 | | -------------- | 19 | | | 20 | | | 21 | | **Designator** | 22 | | J1 | 23 | | | 24 | | | 25 | | **Role** | 26 | | Card reader RS232+5V | 27 | | | 28 | | | 29 | | **Connector** | 30 | | Molex 5268-04 | 31 | | | 32 | | | 33 | | **Housing** | 34 | | Molex 5264-04 | 35 | | | 36 | | | 37 | | **Crimp** | 38 | | Molex 5263 | 39 | 41 | 42 | | **Pin #** | **Pin Name** | Anotation | 43 | | :-------: | -------------- | --------- | 44 | | `1` | `GND` | | 45 | | `2` | `TXD` | billmock In. | 46 | | `3` | `RXD` | billmock out. | 47 | | `4` | `5V` | 5V Power out | 48 | 49 |
51 | 52 | - 5V Power output maximum rating is Peak 2.2A 300mS trip, 1.1A nominal MAX. 53 | 54 | ------------ 55 | 56 | ### Program debugging (SWD/JTAG) 57 | 58 | ![DEBUG](https://billmock.gpark.biz/images/pcb_0v4_mini_port/debug_port.png) 59 | 60 | | | | 61 | | --- | --- | 62 | | **Role** | STM32 SWD | 63 | | **Connector** | TC2030 | 64 | 65 | 66 | 67 | 68 | - Detail information is in [BillMock-HW-RELEASE](https://github.com/pmnxis/BillMock-HW-RELEASE) 69 | -------------------------------------------------------------------------------- /src/semi_layer/timing.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © 2023 Jinwoo Park (pmnxis@gmail.com) 3 | * 4 | * SPDX-License-Identifier: MIT OR Apache-2.0 5 | */ 6 | 7 | use core::cell::UnsafeCell; 8 | 9 | // https://docs.rust-embedded.org/book/concurrency/ 10 | 11 | #[derive(Debug, Clone, Copy)] 12 | pub struct ToggleTiming { 13 | pub high_ms: u16, 14 | pub low_ms: u16, 15 | } 16 | 17 | impl ToggleTiming { 18 | pub const fn default() -> Self { 19 | // default is 100ms high low signal. 20 | ToggleTiming { 21 | high_ms: 100, 22 | low_ms: 100, 23 | } 24 | } 25 | } 26 | 27 | #[derive(Debug)] 28 | pub struct SharedToggleTiming(UnsafeCell); 29 | 30 | impl SharedToggleTiming { 31 | pub const fn new_custom(timing: ToggleTiming) -> Self { 32 | // https://doc.rust-lang.org/reference/const_eval.html#const-functions 33 | Self(UnsafeCell::new(timing)) 34 | } 35 | 36 | pub const fn default() -> Self { 37 | Self(UnsafeCell::new(ToggleTiming::default())) 38 | } 39 | 40 | #[allow(dead_code)] 41 | pub fn set(&self, value: ToggleTiming) { 42 | unsafe { *self.0.get() = value }; 43 | } 44 | 45 | #[allow(dead_code)] 46 | pub fn get(&self) -> ToggleTiming { 47 | unsafe { *self.0.get() } 48 | } 49 | } 50 | 51 | // Required to allow static SharedToggleTiming 52 | // see : https://docs.rust-embedded.org/book/concurrency/#abstractions-send-and-sync 53 | unsafe impl Sync for SharedToggleTiming {} 54 | -------------------------------------------------------------------------------- /book/src/application.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | # Application Logic 8 | 9 | The simplified program and signal input-output flow are as follows:
10 | 11 | ![BillMock Diagram](https://billmock.gpark.biz/images/billmock_logic_diagram_short.png) 12 | 13 | 14 | ## Overview of Operation 15 | This device is designed to enhance payment systems by storing, manipulating, and delaying input/output signals from currency payment devices (such as credit card readers, coin acceptors, bill validators) before connecting them to the GAME I/O PCB. 16 | 17 | By configuring DIP switch settings and making appropriate wiring changes, you can guide the setup between the desired currency payment device and the GAME I/O PCB. This device enables the following configurations: 18 | 19 | - Installing a credit card reader on an existing game machine. 20 | - Using both a credit card reader and a bill validator (or coin acceptor) on 1P (Player 1) in an existing game machine. 21 | - Managing 1P/2P (Player 1/Player 2) when using one card reader and one bill validator (or coin acceptor) on an existing game machine upon pressing the start button. 22 | - Managing 1P/2P (Player 1/Player 2) when using one bill validator (or coin acceptor) on an existing game machine upon pressing the start button. 23 | - Override output pulse duration based DIP switch configuration and input signal. 24 | - Display accumulated card / paper(or coin) count instead of magnetic coin meter. 25 | - Allowing users to make more complex modifications to input/output signals through code customization. 26 | -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: © 2023 Jinwoo Park (pmnxis@gmail.com) 2 | # 3 | # SPDX-License-Identifier: MIT OR Apache-2.0 4 | 5 | [target.'cfg(all(target_arch = "arm", target_os = "none"))'] 6 | # [Mass Production Reminder] 7 | # To address supply availability and cost reduction, 8 | # it is possible to switch to STM32G030C6Tx. 9 | # However, please note that currently, in the debug build, the flash section exceeds 32KB. 10 | # Therefore, this change will be applicable only for production use, 11 | # considering its benefits in the release build. 12 | # runner = "probe-run --chip STM32G030C8Tx --host-log-format \"{t} [{L}] {f}:{l} {s}\"" 13 | runner = [ 14 | "probe-run", 15 | "--chip", 16 | "STM32G030C8Tx", 17 | "--log-format", 18 | "{t} [{L}][ {f}:{l} ] {s}", 19 | ] 20 | 21 | [build] 22 | target = "thumbv6m-none-eabi" 23 | 24 | # As described in the `Cargo.toml` located in the root directory of the project, 25 | # in order to maintain a separation between the NDA code and open-source code, 26 | # the project follows a re-patching approach for incorporating the library source code 27 | # from the local environment into the actual open-source build. 28 | [patch.'https://github.com/pmnxis/billmock-app-rs.git'] 29 | billmock-plug-card = { path = "billmock-plug-card" } 30 | 31 | # Custom println! To use formatting, use the latest branch. 32 | # [patch.crates-io] 33 | # defmt = { git = "https://github.com/knurling-rs/defmt.git", rev = "ca161bf0ab9ea8209fa5b6781e9f4e87f592eb57" } 34 | # defmt-test = { git = "https://github.com/knurling-rs/defmt.git", rev = "ca161bf0ab9ea8209fa5b6781e9f4e87f592eb57" } 35 | # defmt-rtt = { git = "https://github.com/knurling-rs/defmt.git", rev = "ca161bf0ab9ea8209fa5b6781e9f4e87f592eb57" } 36 | -------------------------------------------------------------------------------- /src/components/dip_switch.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © 2023 Jinwoo Park (pmnxis@gmail.com) 3 | * 4 | * SPDX-License-Identifier: MIT OR Apache-2.0 5 | */ 6 | 7 | use embassy_stm32::gpio::{AnyPin, Input}; 8 | use {defmt_rtt as _, panic_probe as _}; 9 | 10 | use crate::types::dip_switch_config::{AppMode0V3, InhibitOverride, TimingOverride}; 11 | 12 | // todo! - auto bouncer with async/await 13 | 14 | #[allow(clippy::type_complexity)] 15 | pub struct DipSwitch { 16 | gpios: ( 17 | Input<'static, AnyPin>, 18 | Input<'static, AnyPin>, 19 | Input<'static, AnyPin>, 20 | Input<'static, AnyPin>, 21 | Input<'static, AnyPin>, 22 | Input<'static, AnyPin>, 23 | ), 24 | } 25 | 26 | #[allow(dead_code)] 27 | impl DipSwitch { 28 | pub const fn new( 29 | in_inhibit0: Input<'static, AnyPin>, 30 | in_inhibit1: Input<'static, AnyPin>, 31 | in_timing0: Input<'static, AnyPin>, 32 | in_timing1: Input<'static, AnyPin>, 33 | in_mode0: Input<'static, AnyPin>, 34 | in_mode1: Input<'static, AnyPin>, 35 | ) -> Self { 36 | Self { 37 | gpios: ( 38 | in_inhibit0, 39 | in_inhibit1, 40 | in_timing0, 41 | in_timing1, 42 | in_mode0, 43 | in_mode1, 44 | ), 45 | } 46 | } 47 | 48 | pub fn read(&self) -> (InhibitOverride, TimingOverride, AppMode0V3) { 49 | ( 50 | InhibitOverride::try_from( 51 | self.gpios.0.is_low() as u8 + self.gpios.1.is_low() as u8 * 2, 52 | ) 53 | .unwrap(), 54 | TimingOverride::try_from(self.gpios.2.is_low() as u8 + self.gpios.3.is_low() as u8 * 2) 55 | .unwrap(), 56 | AppMode0V3::try_from(self.gpios.4.is_low() as u8 + self.gpios.5.is_low() as u8 * 2) 57 | .unwrap(), 58 | ) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/components/start_button.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © 2023 Jinwoo Park (pmnxis@gmail.com) 3 | * 4 | * SPDX-License-Identifier: MIT OR Apache-2.0 5 | */ 6 | 7 | use defmt::unwrap; 8 | use embassy_executor::Spawner; 9 | use embassy_stm32::exti::ExtiInput; 10 | use embassy_stm32::gpio::{AnyPin, Output}; 11 | 12 | use crate::semi_layer::buffered_opendrain::{buffered_opendrain_spawn, BufferedOpenDrain}; 13 | use crate::semi_layer::buffered_wait::buffered_wait_spawn; 14 | use crate::semi_layer::buffered_wait::{BufferedWait, InputEventChannel, RawInputPortKind}; 15 | use crate::semi_layer::timing::SharedToggleTiming; 16 | use crate::types::buffered_opendrain_kind::BufferedOpenDrainKind; 17 | use crate::types::input_port::InputPortKind; 18 | use crate::types::player::Player; 19 | /// deprecated from hardware version 0.3 20 | #[allow(dead_code)] 21 | pub struct StartButton { 22 | in_switch: BufferedWait, 23 | out_led: BufferedOpenDrain, 24 | } 25 | 26 | #[allow(dead_code)] 27 | impl StartButton { 28 | pub const fn new( 29 | player: Player, 30 | in_switch: ExtiInput<'static, AnyPin>, 31 | out_led: Output<'static, AnyPin>, 32 | mpsc_ch: &'static InputEventChannel, 33 | shared_timing: &'static SharedToggleTiming, 34 | ) -> Self { 35 | let led_str: &'static str = BufferedOpenDrainKind::VendSideStartLed(player).const_str(); 36 | let (snj_p, snj_str): (RawInputPortKind, &'static str) = 37 | InputPortKind::StartJam1P.to_raw_and_const_str(player); 38 | 39 | Self { 40 | in_switch: BufferedWait::new(in_switch, mpsc_ch, snj_p, snj_str), 41 | out_led: BufferedOpenDrain::new(out_led, shared_timing, led_str), 42 | } 43 | } 44 | 45 | pub fn start_tasks(&'static self, spawner: &Spawner) { 46 | unwrap!(spawner.spawn(buffered_wait_spawn(&self.in_switch))); 47 | unwrap!(spawner.spawn(buffered_opendrain_spawn(&self.out_led))); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /book/src/feature_disp_hw_info.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | # DisplayRom 8 | 9 | ![display hw info screen](https://billmock.gpark.biz/images/dip_switch_disp_hw_info_enus.png) 10 | 11 | - To view such information during normal operation, please check and configure the [DIP SWITCH execution mode](./dip_switch.md#application-mode-dip-switch-configuration). 12 | 13 | - The information will appear for 10 seconds, divided into four lines, displaying details about ROM and the program: 14 | - Line 1 : `{Boot Cnt} Ver:{x.y.z}` 15 | - **Boot Cnt**: Number of boots for the BillMock hardware. It increments by 1 each time the device is powered off and on. 16 | - **x.y.z**: Firmware version of the BillMock program [billmock-app-rs](https://github.com/pmnxis/billmock-app-rs). 17 | 18 | - Line 2 : `S/N : {Serial Number}` 19 | - **Serial Number**: Unique serial number assigned to the BillMock hardware during mass production. 20 | 21 | - Line 3 : `TID : {TID}` 22 | - **TID**: The unique Terminal ID of the card terminal. It is a value set in the card terminal when connecting it to the payment gateway (PG). It is also a unique identifier in the PG's system. 23 | 24 | - Line 4 : `Uptime : {Uptime} Mins` 25 | - **Uptime**: Represents the duration the BillMock hardware has been powered on, measured in minutes. 26 | 27 | - This feature is available from firmware version `0.2.1` and hardware version `0.4` or `Mini 0.4` onwards. It is not supported on earlier hardware versions. 28 | 29 | - From hardware version 0.5 or Mini 0.5 onwards, you can use the SVC button by pressing 2 seconds. 30 | > ![svc button](https://billmock.gpark.biz/images/svc_button.jpg) 31 | 32 | - This feature is available starting from firmware version `0.2.0` and hardware `0.4` or `Mini 0.4` and later. It is not available for previous hardware versions. 33 | 34 | - When exiting [DispRom](./feature_disp_rom.md) through the DIP switch on hardware version `0.4` or `Mini 0.4` and above, the display is also shown. 35 | -------------------------------------------------------------------------------- /src/components/vend_side_bill.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © 2023 Jinwoo Park (pmnxis@gmail.com) 3 | * 4 | * SPDX-License-Identifier: MIT OR Apache-2.0 5 | */ 6 | 7 | use defmt::unwrap; 8 | use embassy_executor::Spawner; 9 | use embassy_stm32::exti::ExtiInput; 10 | use embassy_stm32::gpio::{AnyPin, Output}; 11 | 12 | use crate::semi_layer::buffered_opendrain::{buffered_opendrain_spawn, BufferedOpenDrain}; 13 | use crate::semi_layer::buffered_wait::buffered_wait_spawn; 14 | use crate::semi_layer::buffered_wait::{BufferedWait, InputEventChannel, RawInputPortKind}; 15 | use crate::semi_layer::timing::SharedToggleTiming; 16 | use crate::types::buffered_opendrain_kind::BufferedOpenDrainKind; 17 | use crate::types::input_port::InputPortKind; 18 | use crate::types::player::Player; 19 | 20 | pub struct VendSideBill { 21 | pub out_inhibit: BufferedOpenDrain, 22 | in_vend: BufferedWait, 23 | in_start_jam: BufferedWait, 24 | } 25 | 26 | impl VendSideBill { 27 | pub const fn new( 28 | player: Player, 29 | out_inhibit: Output<'static, AnyPin>, 30 | in_vend: ExtiInput<'static, AnyPin>, 31 | in_start_jam: ExtiInput<'static, AnyPin>, 32 | mpsc_ch: &'static InputEventChannel, 33 | shared_timing: &'static SharedToggleTiming, 34 | ) -> VendSideBill { 35 | let inhibit_str: &'static str = BufferedOpenDrainKind::VendSideInhibit(player).const_str(); 36 | let (vend_p, vend_str): (RawInputPortKind, &'static str) = 37 | InputPortKind::Vend1P.to_raw_and_const_str(player); 38 | let (snj_p, snj_str): (RawInputPortKind, &'static str) = 39 | InputPortKind::StartJam1P.to_raw_and_const_str(player); 40 | 41 | Self { 42 | out_inhibit: BufferedOpenDrain::new(out_inhibit, shared_timing, inhibit_str), 43 | in_vend: BufferedWait::new(in_vend, mpsc_ch, vend_p, vend_str), 44 | in_start_jam: BufferedWait::new(in_start_jam, mpsc_ch, snj_p, snj_str), 45 | } 46 | } 47 | 48 | pub fn start_tasks(&'static self, spawner: &Spawner) { 49 | unwrap!(spawner.spawn(buffered_opendrain_spawn(&self.out_inhibit))); 50 | unwrap!(spawner.spawn(buffered_wait_spawn(&self.in_vend))); 51 | unwrap!(spawner.spawn(buffered_wait_spawn(&self.in_start_jam))); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /book/src/overview.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | # Overview 8 | 9 | 10 | 11 | "Billmock" is a system designed to manipulate the currency payment I/O signals that arcade machines receive based on specific conditions. It is primarily used in South Korean arcade machines for tasks such as installing credit card reader or enabling programmable operations for sequential tasks based on various conditions. 12 | 13 | To configure it for desired settings on-site, "Billmock" allows preconfigured I/O remapping adjustments through DIP switches. In terms of wiring, it is installed between the HOST GAME PCB and the bill acceptor device. 14 | 15 | ## Hardware 16 | ![Actual BillMock PCB 0v5](https://billmock.gpark.biz/images/BillMockPCB_0v5_mini.jpg) 17 | The hardware revision currently adopted for final mass production is 0.5-MINI, and the software development is also progressing according to this version. 18 | 19 | ![Actual BillMock PCB 0v4](https://billmock.gpark.biz/images/BillMockPCB_0v4.jpg) 20 | There are three previous hardware revisions available: 0.2, 0.3, and 0.4. The development is focused on compatibility with versions 0.3 and 0.4, which are the ones actively in use. Detailed hardware schematics are here 21 | [BillMock-HW-RELEASE](https://github.com/pmnxis/BillMock-HW-RELEASE) 22 | 23 | ## Application 24 | The firmware software has been developed in **Rust**, as opposed to the de facto **C** language. While the choice to use Rust is partly based on trust in its reliability, it also serves the purpose of validating its suitability for embedded systems intended for mass production. Therefore, hope to maintain the firmware source code as a precedent, akin to an example code. 25 | 26 | ## License 27 | 28 | This program and the accompanying materials are made available under the terms of the Apache Software License 2.0 which is available at [Apache Software License 2.0](https://www.apache.org/licenses/LICENSE-2.0), or the MIT license which is available at [MIT License](https://opensource.org/licenses/MIT) 29 | 30 | Also all of codes are based one MIT or Apache Software License 2.0. But some common *.toml files are based on CC0-1.0 license. (Example Cargo.toml) 31 | 32 | -------------------------------------------------------------------------------- /book/src/port_overview.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | # Difference between two hardware types 8 | 9 | | Mini types | regular version | 10 | | ---- | ---- | 11 | | ![mini image](https://billmock.gpark.biz/images/pcb_top_mini_0v4.png) | ![normal image](https://billmock.gpark.biz/images/pcb_top_0v4.png) | 12 | 13 | BillMock hardware comes in two different variants. There is the smaller rectangular-shaped [BillMock Mini (Rectangular)](./port_04_mini_overview.md) and the square-shaped [BillMock (Square)](./port_04_overview.md) with terminals and a DC jack on the top and bottom. 14 | 15 | The Mini version removes the terminals, DC jack, and additional RS232 port compared to the standard version. It comes at a lower price point and is easier to install if you already have the harness prepared. In situations where harness configuration is complex and it's more convenient to connect directly to the terminals on-site, the standard version may be suitable. 16 | 17 | However, for most on-site installations, we recommend the Mini version as it offers greater efficiency, especially when using pre-configured harnesses, making the installation process smoother. 18 | 19 | ## Table of Contents 20 | 21 | - [BillMock Mini (Rectangular)](./port_04_mini_overview.md) 22 | - [Vend side (Top)](./port_04_mini_vend_side.md) 23 | - [Host Side (Bottom)](./port_04_mini_host_side.md) 24 | - [Miscellaneous](./port_04_mini_etc.md) 25 | - [BillMock (Square)](./port_04_overview.md) 26 | - [Vend side (Top)](./port_vend_side.md) 27 | - [Host Side (Bottom)](./port_host_side.md) 28 | - [Miscellaneous](./port_etc.md) 29 | 30 | At the top, there are connectors for the Vend Side, which includes bill paper acceptors, coin acceptors, credit card reader, and similar currency validators. 31 | 32 | At the bottom, connectors are placed for the Host Side, which interfaces with the mainboard of the actual arcade machine, such as the GAME I/O PCB. 33 | 34 | On the left and right sides, you'll find identical connectors arranged in a decalcomania pattern. The connectors on the left are designated for Player 1, while those on the right are for Player 2. 35 | 36 | With this pattern of connectors, it's easy to connect wires to the connectors during actual installation and operation by referring to the layout. 37 | -------------------------------------------------------------------------------- /src/application/io_bypass.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © 2023 Jinwoo Park (pmnxis@gmail.com) 3 | * 4 | * SPDX-License-Identifier: MIT OR Apache-2.0 5 | */ 6 | 7 | use defmt::{error, warn}; 8 | 9 | use super::{DEFAULT_BUSY_ALPHA_TIMING_MS, DEFAULT_VEND_INDICATOR_TIMING_MS}; 10 | use crate::boards::*; 11 | use crate::semi_layer::buffered_wait::InputEventKind; 12 | use crate::types::input_port::{InputEvent, InputPortKind}; 13 | 14 | pub async fn io_bypass(board: &'static Board, event: &InputEvent, override_druation_force: bool) { 15 | let output = match board.correspond_output(&event.port) { 16 | Ok(x) => x, 17 | #[cfg(feature = "svc_button")] 18 | Err(BoardCorrespondOutputMatchError { 19 | origin: InputPortKind::SvcButton, 20 | }) => { 21 | // Svc Button is not output type 22 | return; 23 | } 24 | Err(e) => { 25 | error!("io_bypass some wrong enum value income : {}", e.origin); 26 | return; 27 | } 28 | }; 29 | 30 | let led = board.correspond_indicator(&event.port); 31 | let busy = board.correspond_busy(&event.port); 32 | 33 | match (event.port, event.event) { 34 | (x, InputEventKind::LongPressed(0) | InputEventKind::LongPressed(1)) => { 35 | warn!("{:?} too short pressed", x); 36 | } 37 | (InputPortKind::Vend1P | InputPortKind::Vend2P, InputEventKind::LongPressed(x)) => { 38 | let led_timing = if override_druation_force { 39 | output.tick_tock(1).await; 40 | 41 | DEFAULT_VEND_INDICATOR_TIMING_MS 42 | } else { 43 | let mul10 = (x as u16) * 10; 44 | output.alt_tick_tock(1, mul10, mul10).await; 45 | 46 | mul10 47 | }; 48 | 49 | if let Some(busy) = busy { 50 | busy.one_shot_high_mul(1, led_timing, led_timing, DEFAULT_BUSY_ALPHA_TIMING_MS) 51 | .await; 52 | } 53 | 54 | if let Some(led) = led { 55 | led.alt_tick_tock(1, led_timing, led_timing).await; 56 | } 57 | } 58 | (InputPortKind::StartJam1P | InputPortKind::StartJam2P, _) => { 59 | // skip 60 | } 61 | (_, InputEventKind::Pressed) => { 62 | output.set_high().await; 63 | } 64 | (_, InputEventKind::Released) => { 65 | output.set_low().await; 66 | } 67 | (_, InputEventKind::LongPressed(_)) => { 68 | // skip 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /book/src/port_etc.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | # Port - Miscellaneous 8 | 9 | ## Credit Card Reader Port 10 | 11 | 12 | 13 | 17 | 50 |
14 | 15 | ![J3](https://billmock.gpark.biz/images/pcb_0v4_port/J3.png) 16 | 18 | 19 | | | 20 | | -------------- | 21 | | | 22 | | | 23 | | **Designator** | 24 | | J3 | 25 | | | 26 | | | 27 | | **Role** | 28 | | Card reader RS232+5V | 29 | | | 30 | | | 31 | | **Connector** | 32 | | Molex 5268-04 | 33 | | | 34 | | | 35 | | **Housing** | 36 | | Molex 5264-04 | 37 | | | 38 | | | 39 | | **Crimp** | 40 | | Molex 5263 | 41 | 42 | | **Pin #** | **Pin Name** | Anotation | 43 | | :-------: | -------------- | --------- | 44 | | `1` | `GND` | | 45 | | `2` | `TXD` | billmock In. | 46 | | `3` | `RXD` | billmock out. | 47 | | `4` | `5V` | 5V Power out | 48 | 49 |
51 | 52 | - 5V Power output maximum rating is Peak 2.2A 300mS trip, 1.1A nominal MAX. 53 | 54 | ------------ 55 | 56 | ## DC Power Jack 57 | 58 | 59 | 60 | 64 | 82 |
61 | 62 | ![J1](https://billmock.gpark.biz/images/pcb_0v4_port/J1.png) 63 | 65 | 66 | | | 67 | | -------------- | 68 | | | 69 | | | 70 | | **Designator** | 71 | | J1 | 72 | | | 73 | | | 74 | | **Role** | 75 | | Extra DC Power Jack | 76 | | | 77 | | | 78 | | **Connector** | 79 | | DC Jack 5.5pi - 2.0pi | 80 | 81 |
83 | 84 | - 12V input is recommended (maximum 16V). 85 | - In addition to the bottom DC jack, it is also recommended to receive power through the 10-pin Molex ports on the bottom left (J5) and bottom right (J4). 86 | - Using the top terminal (J9) or the top 10-pin Molex (J7/J8) for power input is not recommended. 87 | 88 | 93 | 94 | ------------ 95 | 96 | ### Program debugging (SWD/JTAG) 97 | 98 | 99 | 100 | 104 | 118 |
101 | 102 | ![DEBUG](https://billmock.gpark.biz/images/pcb_0v4_port/debug_port.png) 103 | 105 | 106 | | | 107 | | -------------- | 108 | | | 109 | | | 110 | | **Role** | 111 | | STM32 SWD | 112 | | | 113 | | | 114 | | **Connector** | 115 | | TC2030 | 116 | 117 |
119 | 120 | - Detail information is in [BillMock-HW-RELEASE](https://github.com/pmnxis/BillMock-HW-RELEASE) 121 | -------------------------------------------------------------------------------- /book/src/dev/hardware.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | # Hardware 8 | 9 | ![Actual BillMock PCB 0v4](https://billmock.gpark.biz/images/BillMockPCB_0v4.jpg) 10 | 11 | There are currently four hardware revisions available: 0.2, 0.3, 0.4, and 0.4 Mini. Development is being carried out specifically for the 0.4 and 0.4 Mini revisions, which are the ones that are actually usable. 12 | 13 | To check pin mappings or installation details, please refer to the desired section in the table of contents on the left. 14 | 15 | For detailed hardware schematics, you can refer to [BillMock-HW-RELEASE](https://github.com/pmnxis/BillMock-HW-RELEASE). 16 | 17 | ## Hardware design 18 | BillMock hardware schematic repository (only pdf) : [BillMock Hardware PDF Release](https://github.com/pmnxis/BillMock-HW-RELEASE) 19 | 20 | The schematic printed in PDF is distributed under CC BY-SA 3.0, but the actual Gerber files and project files are private. 21 | 22 | #### v 0.4 Mini (2023-09-12 or 2023-09-13) 23 | [BillMock-Mini-HW-0v4.pdf](https://github.com/pmnxis/BillMock-HW-RELEASE/blob/master/sch//BillMock-Mini-HW-0v4.pdf) 24 | 25 | #### v 0.4 (2023-09-08) 26 | [BillMock-HW-0v4.pdf](https://github.com/pmnxis/BillMock-HW-RELEASE/blob/master/sch/BillMock-HW-0v4.pdf) 27 | 28 | #### ~~v 0.3 (2023-08-11)~~ - DEPRECATED 29 | ~~[BillMock-HW-0v3.pdf](https://github.com/pmnxis/BillMock-HW-RELEASE/blob/master/sch/BillMock-HW-0v3.pdf)~~ 30 | 31 | #### ~~v 0.2 (2023-06-13)~~ - DEPRECATED 32 | ~~[BillMock-HW-0v2.pdf](https://github.com/pmnxis/BillMock-HW-RELEASE/blob/master/sch/BillMock-HW-0v2.pdf)~~ 33 | 34 | # Hardware License Information 35 | 36 | The original data for the circuit diagram is under a private license, but the PDF schematic is distributed under the CC-BY-SA 3.0 license. Therefore, data such as gerbers and the Bill of Materials (BOM) used in production will not be disclosed at all. Additionally, circuits created based on this schematic (PDF) must adhere to the following conditions: 37 | 38 | Please provide proper attribution when using it for commercial purposes. 39 | 40 | - Attribution: You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use. 41 | 42 | - ShareAlike: If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original. 43 | 44 | Translation Note: This text appears to describe the licensing terms and conditions related to the hardware, particularly regarding the use of schematic data. 45 | -------------------------------------------------------------------------------- /src/semi_layer/buffered_wait_receiver.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © 2023 Jinwoo Park (pmnxis@gmail.com) 3 | * 4 | * SPDX-License-Identifier: MIT OR Apache-2.0 5 | */ 6 | 7 | use bit_field::BitField; 8 | use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; 9 | use embassy_sync::blocking_mutex::Mutex; 10 | use embassy_sync::channel::Channel; 11 | use embassy_sync::channel::TryReceiveError; 12 | 13 | use super::buffered_wait::{InputEventChannel, InputEventKind, RawInputEvent}; 14 | 15 | pub struct BufferedWaitReceiver { 16 | pub channel: InputEventChannel, 17 | bit_cache: Mutex, 18 | } 19 | 20 | impl BufferedWaitReceiver { 21 | pub const fn new() -> Self { 22 | Self { 23 | channel: Channel::new(), 24 | bit_cache: Mutex::new(0), 25 | } 26 | } 27 | 28 | pub fn try_receive(&'static self) -> Result { 29 | let received = self.channel.try_receive()?; 30 | let event = InputEventKind::from(received.event); 31 | 32 | let port_num = received.port as usize; 33 | match event { 34 | InputEventKind::Pressed => { 35 | self.bit_cache.lock(|x| { 36 | let mut x = *x; 37 | x.set_bit(port_num, true); 38 | }); 39 | } 40 | InputEventKind::Released => { 41 | self.bit_cache.lock(|x| { 42 | let mut x = *x; 43 | x.set_bit(port_num, false); 44 | }); 45 | } 46 | _ => {} 47 | }; 48 | 49 | Ok(received) 50 | } 51 | 52 | #[allow(dead_code)] 53 | pub async fn recv(&'static self) -> RawInputEvent { 54 | let received = self.channel.receive().await; 55 | let event = InputEventKind::from(received.event); 56 | 57 | let port_num = received.port as usize; 58 | match event { 59 | InputEventKind::Pressed => { 60 | self.bit_cache.lock(|x| { 61 | let mut x = *x; 62 | x.set_bit(port_num, true); 63 | }); 64 | } 65 | InputEventKind::Released => { 66 | self.bit_cache.lock(|x| { 67 | let mut x = *x; 68 | x.set_bit(port_num, false); 69 | }); 70 | } 71 | _ => {} 72 | }; 73 | 74 | received 75 | } 76 | 77 | pub fn get_cache(&self) -> u16 { 78 | self.bit_cache.lock(|x| *x) 79 | } 80 | 81 | #[allow(dead_code)] 82 | pub fn get_cache_optional(&self, other: u16) -> Option { 83 | let me = self.get_cache(); 84 | if me == other { 85 | None 86 | } else { 87 | Some(me) 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /book/src/feature_disp_rom.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | # DisplayRom 8 | 9 | ![display rom screen](https://billmock.gpark.biz/images/dip_switch_rom_disp_enus.png) 10 | 11 | - To view such information during normal operation, please check and configure the [DIP SWITCH execution mode](./dip_switch.md#application-mode-dip-switch-configuration). 12 | 13 | - The information will appear for 10 seconds, divided into four lines, displaying details about ROM and the program: 14 | - Line 1: `{Git Hash} {TID}` 15 | - **Git Hash**: The shortened git commit hash of the BillMock firmware program [billmock-app-rs](https://github.com/pmnxis/billmock-app-rs). 16 | - **TID**: The unique Terminal ID of the card terminal. It is a value set in the card terminal when connecting it to the payment gateway (PG). It is also a unique identifier in the PG's system. 17 | 18 | - Line 2: `Cards: {1P Count} + {2P Count} = {Credit Sum}` 19 | - **1P Count**: Accumulated clock counts for Player **1** based on pre-configured clock settings and consumer payments on the card terminal. 20 | - **2P Count**: Accumulated clock counts for Player **2** based on pre-configured clock settings and consumer payments on the card terminal. 21 | - **Credit Sum**: The sum of 1P Count and 2P Count, representing credits processed by the card terminal. 22 | 23 | - Line 3: `Bills: {1P Count} + {2P Count} = {Coin Sum}` 24 | - **1P Count**: Accumulated clock counts for Player **1** based on bill acceptor or coin acceptor clock settings and consumer payments. 25 | - **2P Count**: Accumulated clock counts for Player **2** based on bill acceptor or coin acceptor clock settings and consumer payments. 26 | - **Coin Sum**: The sum of 1P Count and 2P Count, representing currency processed by the bill acceptor or coin acceptor. 27 | 28 | - Line 4: `1P: {1P Sum Count}, {2P Sum Count}` 29 | - **1P Sum Count**: The total clock counts accumulated for Player **1** by the card terminal and bill acceptor (or coin acceptor).
It's the sum of 1P Count from the second and third lines. 30 | - **2P Sum Count**: The total clock counts accumulated for Player **2** by the card terminal and bill acceptor (or coin acceptor).
It's the sum of 2P Count from the second and third lines. 31 | 32 | - All count numbers are displayed in 6 digits (0 ~ 999,999). If the count exceeds 1,000,000, it will be displayed as 000,000, and the next count after that will increment normally, such as 000,001. 33 | 34 | - This feature is available starting from firmware version `0.2.0` and hardware `0.4` or `Mini 0.4` and later. It is not available for previous hardware versions. 35 | 36 | - From hardware version 0.5 or Mini 0.5 onwards, you can use the SVC button by pressing it briefly. 37 | > ![svc button](https://billmock.gpark.biz/images/svc_button.jpg) -------------------------------------------------------------------------------- /book/src/port_04_mini_overview.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | # BillMock Mini 8 | 9 | ![real photo](https://billmock.gpark.biz/images/pcb_0v4_mini_port/BillMockPCB_0v4_mini_rectangle.jpg) 10 | 11 | | Front side | Back side | 12 | | ---- | ---- | 13 | | ![top side image](https://billmock.gpark.biz/images/pcb_top_mini_0v4.png) | ![bottom side image](https://billmock.gpark.biz/images/pcb_bot_mini_0v4.png) | 14 | 15 | ## Pin map overview 16 | 17 | ![Port Quick Look](https://billmock.gpark.biz/images/pcb_0v4_mini_port/mini_port_quick_look.png) 18 | 19 | At the top, there are connectors for the Vend Side, which includes bill paper acceptors, coin acceptors, credit card reader, and similar currency validators. 20 | 21 | At the bottom, connectors are placed for the Host Side, which interfaces with the mainboard of the actual arcade machine, such as the GAME I/O PCB. 22 | 23 | On the left and right sides, you'll find identical connectors arranged in a decalcomania pattern. The connectors on the left are designated for Player 1, while those on the right are for Player 2. 24 | 25 | With this pattern of connectors, it's easy to connect wires to the connectors during actual installation and operation by referring to the layout. 26 | 27 | From a conceptual perspective, in the existing wiring, the connectors that were originally connected from top to bottom are each separated and connected to the upper and lower connectors. The hardware and software manage the communication between these upper and lower connectors. This design was intentional during the connection setup. 28 | 29 | ## Simplified Connection of BillMock Mini 30 | ![Simplified Wiring](https://billmock.gpark.biz/images/pcb_0v4_mini_port/mini_wiring.png) 31 | 32 | - WARN : The wire shapes are symbolic representations and do not indicate the actual wire colors used in the image. Please refer to detailed pinouts in the respective pages. 33 | 34 | This configuration involves installing this PCB (BillMock) between the existing bill acceptor (or coin acceptor) on the top and the GAME I/O PCB on the bottom, with a harness connection. 35 | 36 | In some cases, additional wiring work may be required, but considering the widely used bill acceptor wiring in South Korea, you can prepare the harness and connect it accordingly. 37 | 38 | ## Specifications 39 | | | | 40 | | ----------- | ------------ | 41 | | Product name| BillMock | 42 | | Manufacturer| GPARK Co., Ltd. | 43 | | Country | South Korea | 44 | | Dimension | 75.0 mm * 45.0 mm | 45 | | MCU | STM32G030C8 | 46 | | MCU Spec | ARM Cortex-M0+ 64Mhz CPU, 8KiB SRAM, 64KiB Flash | 47 | | Software | Embassy-rs written in rust | 48 | | Power Input | 12V 2A | 49 | | Pouwer Output | 5V (Peak 2.2A 300mS trip, 1.1A nominal MAX) - Credit card reader power | 50 | 51 | - WARN : The input power allows up to 16V, but please be cautious as it is directly passed through to the bill handling device. 52 | -------------------------------------------------------------------------------- /book/src/port_04_mini_vend_side.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | # Vend side port map 8 | 9 | ## Vend Side Player 1 Port (left) 10 | 11 | ![J4](https://billmock.gpark.biz/images/pcb_0v4_mini_port/J4.png) 12 | 13 | | | | 14 | | -------------- | -------------- | 15 | | Designator | J4 | 16 | | | Player 1 side existing
coin and bill acceptor port | 17 | | Connector | Molex 53014-10xx | 18 | | Housing | Molex 51004-10xx | 19 | | Crimp | Molex 50011 | 20 | 21 | | **Pin #** | **Pin Name** | Annotation | 22 | | :-------: | ------------ | ---------------------------------------------- | 23 | | `1` | N/C | Not Connected | 24 | | `2` | `VEND` | Bill acceptor Coin Insertion Signal | 25 | | `3` | N/C | Not Connected | 26 | | `4` | `START` | Start Button Input Signal | 27 | | `5` | `INHIBIT` | Bill acceptor Inhibit (Deactivate) Output Signal | 28 | | `6` | `GND` | Bill acceptor - Pole Power (Input/Output) | 29 | | `7` | N/C | Not Connected | 30 | | `8` | `12V` | Bill acceptor + Pole Power (Input/Output) | 31 | | `9` | `12V` | Bill acceptor + Pole Power (Input/Output) | 32 | | `10` | `GND` | Bill acceptor - Pole Power (Input/Output) | 33 | 34 | - Pins are counted from the left. 35 | - START can be changed to JAM based on DIP switch settings 36 | 37 | ------------ 38 | 39 | ## Vend Side Player 2 Port (right) 40 | 41 | ![J5](https://billmock.gpark.biz/images/pcb_0v4_mini_port/J5.png) 42 | 43 | | | | 44 | | -------------- | -------------- | 45 | | Designator | J5 | 46 | | | Player 2 side existing
coin and bill acceptor port | 47 | | Connector | Molex 53014-10xx | 48 | | Housing | Molex 51004-10xx | 49 | | Crimp | Molex 50011 | 50 | 51 | | **Pin #** | **Pin Name** | Annotation | 52 | | :-------: | ------------ | ---------------------------------------------- | 53 | | `1` | N/C | Not Connected | 54 | | `2` | `VEND` | Bill acceptor Coin Insertion Signal | 55 | | `3` | N/C | Not Connected | 56 | | `4` | `START` | Start Button Input Signal | 57 | | `5` | `INHIBIT` | Bill acceptor Inhibit (Deactivate) Output Signal | 58 | | `6` | `GND` | Bill acceptor - Pole Power (Input/Output) | 59 | | `7` | N/C | Not Connected | 60 | | `8` | `12V` | Bill acceptor + Pole Power (Input/Output) | 61 | | `9` | `12V` | Bill acceptor + Pole Power (Input/Output) | 62 | | `10` | `GND` | Bill acceptor - Pole Power (Input/Output) | 63 | 64 | - Pins are counted from the left. 65 | - START can be changed to JAM based on DIP switch settings 66 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © 2023 Jinwoo Park (pmnxis@gmail.com) 3 | * 4 | * SPDX-License-Identifier: MIT OR Apache-2.0 5 | */ 6 | 7 | #![no_main] 8 | #![no_std] 9 | #![feature(const_trait_impl)] 10 | #![feature(async_fn_in_trait)] 11 | #![allow(stable_features)] 12 | #![feature(type_alias_impl_trait)] 13 | #![feature(impl_trait_in_assoc_type)] 14 | #![feature(effects)] // see : https://github.com/rust-lang/rust/issues/114808 15 | 16 | mod application; 17 | mod boards; 18 | mod components; 19 | mod mp_fingerprint; 20 | mod semi_layer; 21 | mod types; 22 | 23 | use embassy_executor::Spawner; 24 | use embassy_time::{Duration, Timer}; 25 | use static_cell::make_static; 26 | use {defmt_rtt as _, panic_probe as _}; 27 | 28 | use crate::application::Application; 29 | use crate::boards::*; 30 | use crate::components::eeprom::select; 31 | 32 | async fn initial_eeprom(eeprom: &crate::components::eeprom::Novella) { 33 | // uncomment me when you need reset eeprom. 34 | // eeprom.factory_reset(); 35 | 36 | let eeprom_result = eeprom.init(); 37 | match eeprom_result { 38 | Ok(crate::components::eeprom::NovellaInitOk::FirstBoot) => { 39 | defmt::info!("Welcom first boot"); 40 | } 41 | Ok(crate::components::eeprom::NovellaInitOk::PartialSucess(x, y)) => { 42 | defmt::error!("Novella Ok But : {}, {}", x, y); 43 | } 44 | Err(crate::components::eeprom::NovellaInitError::FirstBoot) => { 45 | defmt::error!("FirstBoot"); 46 | } 47 | Err(crate::components::eeprom::NovellaInitError::MissingEeprom) => { 48 | defmt::error!("MissingEeprom"); 49 | } 50 | Ok(crate::components::eeprom::NovellaInitOk::Success(_)) => { 51 | defmt::info!("Eeprom is good status"); 52 | } 53 | }; 54 | 55 | let boot_cnt = eeprom.lock_read(select::HW_BOOT_CNT).await; 56 | eeprom.lock_write(select::HW_BOOT_CNT, boot_cnt + 1).await; 57 | let boot_cnt_after = eeprom.lock_read(select::HW_BOOT_CNT).await; 58 | let uptime = eeprom.get_uptime(); 59 | let uptime_secs = uptime.as_secs(); 60 | 61 | defmt::info!("Boot Count : {} -> {}", boot_cnt, boot_cnt_after,); 62 | defmt::info!( 63 | "Total Uptime : {} ticks <-> {} days {} hrs {} mins {} secs", 64 | uptime, 65 | uptime_secs / (3600 * 24), 66 | (uptime_secs / 3600) % 24, 67 | (uptime_secs / 60) % 60, 68 | uptime_secs % 60 69 | ); 70 | } 71 | 72 | #[embassy_executor::main] 73 | async fn main(spawner: Spawner) { 74 | // Initialize necessary BSP 75 | let board: &mut Board = make_static!(Board::init()); 76 | 77 | // init hardware eeprom 78 | // Count up boot count and show uptime though DAP. 79 | initial_eeprom(&board.hardware.eeprom).await; 80 | 81 | // heuristic wait for stablize external electronic status 82 | Timer::after(Duration::from_millis(1000)).await; 83 | 84 | // Spawns a task bound to the BSP 85 | board.start_tasks(&spawner); 86 | 87 | // heuristic wait for stablize task spawning 88 | Timer::after(Duration::from_millis(500)).await; 89 | 90 | defmt::info!("Hello BillMock"); 91 | 92 | let application = Application::new(board); 93 | application.main_task().await; 94 | } 95 | 96 | #[cfg(test)] 97 | mod tests {} 98 | -------------------------------------------------------------------------------- /src/components/host_side_bill.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © 2023 Jinwoo Park (pmnxis@gmail.com) 3 | * 4 | * SPDX-License-Identifier: MIT OR Apache-2.0 5 | */ 6 | 7 | use defmt::unwrap; 8 | use embassy_executor::Spawner; 9 | use embassy_stm32::exti::ExtiInput; 10 | use embassy_stm32::gpio::{AnyPin, Output}; 11 | 12 | use crate::semi_layer::buffered_opendrain::{buffered_opendrain_spawn, BufferedOpenDrain}; 13 | #[cfg(not(feature = "hotfix_hwbug_host_inhibit_floating"))] 14 | use crate::semi_layer::buffered_wait::buffered_wait_spawn; 15 | use crate::semi_layer::buffered_wait::{BufferedWait, InputEventChannel, RawInputPortKind}; 16 | use crate::semi_layer::timing::SharedToggleTiming; 17 | use crate::types::buffered_opendrain_kind::BufferedOpenDrainKind; 18 | use crate::types::input_port::InputPortKind; 19 | use crate::types::player::Player; 20 | 21 | pub struct HostSideBill { 22 | #[cfg_attr(feature = "hotfix_hwbug_host_inhibit_floating", allow(unused))] 23 | in_inhibit: BufferedWait, 24 | pub out_busy: BufferedOpenDrain, 25 | pub out_vend: BufferedOpenDrain, 26 | pub out_jam: BufferedOpenDrain, 27 | pub out_start: BufferedOpenDrain, 28 | } 29 | 30 | impl HostSideBill { 31 | #[allow(clippy::all)] 32 | pub const fn new( 33 | player: Player, 34 | in_inhibit: ExtiInput<'static, AnyPin>, 35 | out_busy: Output<'static, AnyPin>, 36 | out_vend: Output<'static, AnyPin>, 37 | out_jam: Output<'static, AnyPin>, 38 | out_start: Output<'static, AnyPin>, 39 | mpsc_ch: &'static InputEventChannel, 40 | shared_timing: &'static SharedToggleTiming, 41 | ) -> Self { 42 | let (inh_p, inh_str): (RawInputPortKind, &'static str) = 43 | InputPortKind::Inhibit1P.to_raw_and_const_str(player); 44 | let busy_str: &'static str = BufferedOpenDrainKind::HostSideOutBusy(player).const_str(); 45 | let vend_str: &'static str = BufferedOpenDrainKind::HostSideOutVend(player).const_str(); 46 | let jam_str: &'static str = BufferedOpenDrainKind::HostSideOutJam(player).const_str(); 47 | let start_str: &'static str = BufferedOpenDrainKind::HostSideOutStart(player).const_str(); 48 | 49 | Self { 50 | in_inhibit: BufferedWait::new(in_inhibit, mpsc_ch, inh_p, inh_str), 51 | out_busy: BufferedOpenDrain::new(out_busy, shared_timing, busy_str), 52 | out_vend: BufferedOpenDrain::new(out_vend, shared_timing, vend_str), 53 | out_jam: BufferedOpenDrain::new(out_jam, shared_timing, jam_str), 54 | out_start: BufferedOpenDrain::new(out_start, shared_timing, start_str), 55 | } 56 | } 57 | 58 | #[allow(dead_code)] 59 | pub async fn set_bulk_signal_all(&self, busy: bool, vend: bool, jam: bool, start: bool) { 60 | self.out_busy.set_level(busy).await; 61 | self.out_vend.set_level(vend).await; 62 | self.out_jam.set_level(jam).await; 63 | self.out_start.set_level(start).await; 64 | } 65 | 66 | pub fn start_tasks(&'static self, spawner: &Spawner) { 67 | unwrap!(spawner.spawn(buffered_opendrain_spawn(&self.out_busy))); 68 | unwrap!(spawner.spawn(buffered_opendrain_spawn(&self.out_vend))); 69 | unwrap!(spawner.spawn(buffered_opendrain_spawn(&self.out_jam))); 70 | unwrap!(spawner.spawn(buffered_opendrain_spawn(&self.out_start))); 71 | 72 | #[cfg(not(feature = "hotfix_hwbug_host_inhibit_floating"))] 73 | unwrap!(spawner.spawn(buffered_wait_spawn(&self.in_inhibit))); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /book/src/port_04_overview.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | # BillMock 8 | 9 | ![real photo](https://billmock.gpark.biz/images/pcb_0v4_port/BillMockPCB_0v4_square.jpg) 10 | 11 | | Front side | Back side | 12 | | ---- | ---- | 13 | | ![top side image](https://billmock.gpark.biz/images/pcb_top_0v4.png) | ![bottom side image](https://billmock.gpark.biz/images/pcb_bot_0v4.png) | 14 | 15 | ## Pin map overview 16 | 17 | ![Port Quick Look](https://billmock.gpark.biz/images/pcb_0v4_port/port_quick_look.png) 18 | 19 | At the top, there are connectors for the Vend Side, which includes bill paper acceptors, coin acceptors, credit card reader, and similar currency validators. 20 | 21 | At the bottom, connectors are placed for the Host Side, which interfaces with the mainboard of the actual arcade machine, such as the GAME I/O PCB. 22 | 23 | On the left and right sides, you'll find identical connectors arranged in a decalcomania pattern. The connectors on the left are designated for Player 1, while those on the right are for Player 2. 24 | 25 | With this pattern of connectors, it's easy to connect wires to the connectors during actual installation and operation by referring to the layout. 26 | 27 | From a conceptual perspective, in the existing wiring, the connectors that were originally connected from top to bottom are each separated and connected to the upper and lower connectors. The hardware and software manage the communication between these upper and lower connectors. This design was intentional during the connection setup. 28 | 29 | In addition to these connectors, there are connectors for power supply, an additional RS232 connector for debugging, and an SWD (JTAG) connector for program debugging. 30 | 31 | ## Simplified Connection of BillMock 32 | ![Simplified Wiring](https://billmock.gpark.biz/images/pcb_0v4_port/wiring.png) 33 | 34 | - WARN : The wire shapes are symbolic representations and do not indicate the actual wire colors used in the image. Please refer to detailed pinouts in the respective pages. 35 | 36 | This configuration involves installing this PCB (BillMock) between the existing bill acceptor (or coin acceptor) on the top and the GAME I/O PCB on the bottom, with a harness connection. 37 | 38 | In some cases, additional wiring work may be required, but considering the widely used bill acceptor wiring in South Korea, you can prepare the harness and connect it accordingly. 39 | 40 | Unlike the BillMock Mini version, you can also perform wiring work through terminals. For more details, please refer to the [Machine Installation](./installation.md) document. 41 | 42 | Additionally, the terminal pinouts are slightly different from standard Molex connectors, so please review this document for details on the top and bottom terminals: 43 | 44 | - [Vend side (Top) Terminal](./port_vend_side.md#vend-side-quick-terminal) 45 | - [Host Side (Bottom) Terminal](./port_host_side.md#host-side-quick-terminal) 46 | 47 | ## Specifications 48 | 49 | | | | 50 | | ----------- | ------------ | 51 | | Product name| BillMock | 52 | | Manufacturer| GPARK Co., Ltd. | 53 | | Country | South Korea | 54 | | Dimension | 65.0 mm * 65.0 mm | 55 | | MCU | STM32G030C8 | 56 | | MCU Spec | ARM Cortex-M0+ 64Mhz CPU, 8KiB SRAM, 64KiB Flash | 57 | | Software | Embassy-rs written in rust | 58 | | Power Input | 12V 2A | 59 | | Pouwer Output | 5V (Peak 2.2A 300mS trip, 1.1A nominal MAX) - Credit card reader power | 60 | 61 | - WARN : The input power allows up to 16V, but please be cautious as it is directly passed through to the bill handling device. 62 | -------------------------------------------------------------------------------- /book/src/port_04_mini_host_side.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | # Host side 핀 아웃 8 | 9 | ## Host Side Player 1 Port (left) 10 | 11 | ![J3](https://billmock.gpark.biz/images/pcb_0v4_mini_port/J3.png) 12 | 13 | | | | 14 | | -------------- | -------------- | 15 | | Designator | J3 | 16 | | | Emulated Player 1 side connector towards the GAME IO side | 17 | | Connector | 141R-2.54-8P | 18 | 19 | | **Pin #** | **Pin Name** | Annotation | 20 | | :-------: | -------------- | ------------------------------------------------------ | 21 | | `1` | `V1-BUSY` | Emulated Busy state Output Signal | 22 | | `2` | `V1-VEND` | Emulated Bill acceptor 1P Coin Insertion Output Signal | 23 | | `3` | `V1-JAM` | Emulated JAM Output Signal | 24 | | `4` | `V1-START` | Emulated 1P Start Button Output Signal | 25 | | `5` | `V1-INHIBIT` | Inhibit Input Signal from 1P GAME I/O | 26 | | `6` | N/C | Not Connected | 27 | | `7` | N/C | Not Connected | 28 | | `8` | `12V` | Product + Pole Power Input | 29 | | `9` | `12V` | Product + Pole Power Input | 30 | | `10` | `GND` | Product - Pole Power Input | 31 | 32 | 33 | - Pins are counted from the right. 34 | - You can also input power directly into BillMock-HW through `12V` and `GND` pins. 35 | - The power pins from this port cannot be used for power output. When power input to this port is blocked, reverse voltage does not flow. 36 | - The "Busy" output signal remains active low from the moment a payment signal is received from the credit card or when the VEND input signal goes active low until the VEND output signal toggles and completes. 37 | 38 | ------------ 39 | 40 | ## Host Side Player 2 Port (right) 41 | 42 | ![J2](https://billmock.gpark.biz/images/pcb_0v4_mini_port/J2.png) 43 | 44 | | | | 45 | | -------------- | -------------- | 46 | | Designator | J2 | 47 | | | Emulated Player 2 side connector towards the GAME IO side | 48 | | Connector | 141R-2.54-8P | 49 | 50 | | **Pin #** | **Pin Name** | Annotation | 51 | | :-------: | -------------- | ------------------------------------------------------ | 52 | | `1` | `V1-BUSY` | Emulated Busy state Output Signal | 53 | | `2` | `V1-VEND` | Emulated Bill acceptor 2P Coin Insertion Output Signal | 54 | | `3` | `V1-JAM` | Emulated JAM Output Signal | 55 | | `4` | `V1-START` | Emulated 2P Start Button Output Signal | 56 | | `5` | `V1-INHIBIT` | Inhibit Input Signal from 2P GAME I/O | 57 | | `6` | N/C | Not Connected | 58 | | `7` | N/C | Not Connected | 59 | | `8` | `12V` | Product + Pole Power Input | 60 | | `9` | `12V` | Product + Pole Power Input | 61 | | `10` | `GND` | Product - Pole Power Input | 62 | 63 | - Pins are counted from the right. 64 | - You can also input power directly into BillMock-HW through `12V` and `GND` pins. 65 | - The power pins from this port cannot be used for power output. When power input to this port is blocked, reverse voltage does not flow. 66 | - The "Busy" output signal remains active low from the moment a payment signal is received from the credit card or when the VEND input signal goes active low until the VEND output signal toggles and completes. 67 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: © 2023 Jinwoo Park (pmnxis@gmail.com) 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | 5 | [package] 6 | name = "billmock-app-rs" 7 | edition = "2021" 8 | version = "0.4.0" 9 | authors = ["Jinwoo Park "] 10 | license = "MIT OR Apache-2.0" 11 | description = "application side of billmock hardware, powered by rust-embedded" 12 | 13 | # feature name starting with "hw_" is reserved for mass production config generator 14 | [features] 15 | default = ["billmock_default"] 16 | hotfix_hwbug_host_inhibit_floating = [] # #19 bug (https://github.com/pmnxis/billmock-app-rs/issues/19) 17 | billmock_default = ["hw_mini_0v5"] # To use rust-analzyer utilizing noDefaultFeatures on vscode 18 | eeprom = [] 19 | svc_button = [] # SVC button 20 | hw_0v2 = ["hotfix_hwbug_host_inhibit_floating"] 21 | hw_0v3 = ["hotfix_hwbug_host_inhibit_floating"] 22 | hw_0v4 = ["eeprom"] 23 | hw_mini_0v4 = ["eeprom"] 24 | hw_mini_0v5 = ["eeprom", "svc_button"] 25 | 26 | [dependencies] 27 | embassy-sync = { version = "0.6.0", features = ["defmt"] } 28 | embassy-executor = { version = "0.6.0", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } 29 | embassy-futures = { version = "0.1.0", features = ["defmt"] } 30 | embassy-time = { version = "0.3.0", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } 31 | embassy-stm32 = { version = "0.1.0", features = ["defmt", "time-driver-any", "stm32g030c8", "memory-x", "unstable-pac", "exti", "time"] } # "unstable-traits" for use InputPin trait for gpio 32 | embassy-embedded-hal = { version = "^0.2.0" } 33 | defmt = "0.3.6" 34 | defmt-rtt = "0.4" 35 | 36 | cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } # 0.7.6 37 | cortex-m-rt = "0.7.3" # 0.7.0 38 | panic-probe = { version = "0.3", features = ["print-defmt"] } 39 | futures = { version = "0.3.17", default-features = false, features = ["async-await"] } 40 | static_cell = { version = "1.3", features = ["nightly"] } 41 | num_enum = { version = "0.7.0", default-features = false } # Application specific import (only `no_std` crates alllowed) 42 | bit_field = "0.10" 43 | nonmax = { version = "0.5.3", default-features = false, features = [] } # to use common NonMax 44 | static_assertions = "1.1.0" 45 | env_to_array = { git = "https://github.com/pmnxis/env-to-array.git", branch = "dynamic_array_patch", features = ["hex"] } 46 | zeroable = "0.2.0" 47 | const-zero = "0.1.1" 48 | 49 | # Application specific library 50 | 51 | # card-terminal-adapter = { path = "card-terminal-adapter" } 52 | # billmock-plug-card = { path = "serial-arcade-example" } 53 | 54 | # The above dependency configurations are intentionally set to an external address in this repository 55 | # for the purpose of compiling both the original and NDA code simultaneously. 56 | # However, in reality, during a regular build, due to the patched content in `.cargo/config.toml`, 57 | # the code accesses the locally stored directory directly without connecting to the URL. 58 | # `billmock-plug-card` would be replaced to NDA library that working on real field with dependency injection 59 | # details : https://doc.rust-lang.org/cargo/reference/overriding-dependencies.html#the-patch-section 60 | 61 | card-terminal-adapter = { path = "card-terminal-adapter" } 62 | billmock-plug-card = { git = "https://github.com/pmnxis/billmock-app-rs.git" } 63 | billmock-otp-dev-info = { git = "https://github.com/pmnxis/billmock-mptool.git" } 64 | 65 | [build-dependencies] 66 | git2 = "0.18" # Git library for Rust 67 | cargo_metadata = "0.18" 68 | mp-fingerprint-type = { git = "https://github.com/pmnxis/billmock-mptool.git" } 69 | hex = "0.4" 70 | card-terminal-adapter = { path = "card-terminal-adapter" } 71 | billmock-plug-card = { git = "https://github.com/pmnxis/billmock-app-rs.git" } 72 | 73 | [profile.release] 74 | codegen-units = 1 75 | debug = 0 76 | debug-assertions = false # <- 77 | lto = 'fat' 78 | opt-level = "s" # or "z" 79 | overflow-checks = false # <- 80 | 81 | [profile.dev] 82 | codegen-units = 1 83 | debug = 2 84 | debug-assertions = true # <- 85 | lto = 'fat' 86 | opt-level = "s" 87 | overflow-checks = true # <- 88 | -------------------------------------------------------------------------------- /src/types/buffered_opendrain_kind.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © 2023 Jinwoo Park (pmnxis@gmail.com) 3 | * 4 | * SPDX-License-Identifier: MIT OR Apache-2.0 5 | */ 6 | 7 | use crate::types::const_convert::ConstInto; 8 | use crate::types::player::Player; 9 | 10 | #[cfg(debug_assertions)] 11 | const OUTPUT_NAME_STRS: [&str; 15] = [ 12 | "HostOut_P1- Busy", // 0 13 | "HostOut_P2- Busy", 14 | "HostOut_P1- Vend", // 2 15 | "HostOut_P2- Vend", 16 | "HostOut_P1- Jam", // 4 17 | "HostOut_P2- Jam", 18 | "HostOut_P1- Start", // 6 19 | "HostOut_P2- Start", 20 | "VendOut_P1- Inhibit", // 8 21 | "VendOut_P2- Inhibit", 22 | "VendOut_P1-StartLED", // 10, would be not use 23 | "VendOut_P2-StartLED", // would be not use 24 | " LED1-Indicator", // 12 25 | " LED2-Indicator", 26 | "Unknown", 27 | ]; 28 | 29 | #[cfg(not(debug_assertions))] 30 | #[rustfmt::skip] 31 | /// reduced output names 32 | const OUTPUT_NAME_STRS: [&str; 15] = [ 33 | "P1H-oBSY", // 0 34 | "P2H-oBSY", 35 | "P1H-oVND", // 2 36 | "P2H-oVND", 37 | "P1H-oJAM", // 4 38 | "P2H-oJAM", 39 | "P1H-oSTR", // 6 40 | "P2H-oSTR", 41 | "P1V-oINH", // 8 42 | "P2V-oINH", 43 | "P1V-oSLD", // 10, would be not use 44 | "P2V-oSLD", // would be not use 45 | "LED1", // 12 46 | "LED2", 47 | "Unknown", 48 | ]; 49 | 50 | #[allow(unused)] 51 | pub enum BufferedOpenDrainKind { 52 | HostSideOutBusy(Player), 53 | HostSideOutVend(Player), 54 | HostSideOutJam(Player), 55 | HostSideOutStart(Player), 56 | VendSideInhibit(Player), 57 | VendSideStartLed(Player), // deprecated in 0.3 58 | Indicator(u8), 59 | Unknown, 60 | } 61 | 62 | impl ConstInto<&'static str> for &BufferedOpenDrainKind { 63 | fn const_into(self) -> &'static str { 64 | OUTPUT_NAME_STRS[self.get_str_idx()] 65 | } 66 | } 67 | 68 | impl BufferedOpenDrainKind { 69 | pub const fn get_str_idx(&self) -> usize { 70 | /* 71 | // following code consume 444 bytes additionaly 72 | match self { 73 | Self::HostSideOutBusy(Player::Player1) => 0, 74 | Self::HostSideOutBusy(Player::Player2) => 1, 75 | Self::HostSideOutVend(Player::Player1) => 2, 76 | Self::HostSideOutVend(Player::Player2) => 3, 77 | Self::HostSideOutJam(Player::Player1) => 4, 78 | Self::HostSideOutJam(Player::Player2) => 5, 79 | Self::HostSideOutStart(Player::Player1) => 6, 80 | Self::HostSideOutStart(Player::Player2) => 7, 81 | Self::VendSideInhibit(Player::Player1) => 8, 82 | Self::VendSideInhibit(Player::Player2) => 9, 83 | Self::VendSideStartLed(Player::Player1) => 10, 84 | Self::VendSideStartLed(Player::Player2) => 11, 85 | Self::Indicator(0) => 12, 86 | Self::Indicator(1) => 13, 87 | _ => 14, 88 | } 89 | */ 90 | 91 | let (a, b): (u8, u8) = match self { 92 | Self::HostSideOutBusy(p) => (0, *p as u8), 93 | Self::HostSideOutVend(p) => (2, *p as u8), 94 | Self::HostSideOutJam(p) => (4, *p as u8), 95 | Self::HostSideOutStart(p) => (6, *p as u8), 96 | Self::VendSideInhibit(p) => (8, *p as u8), 97 | Self::VendSideStartLed(p) => (10, *p as u8), 98 | Self::Indicator(p) => (12, *p), 99 | _ => (14, 0), 100 | }; 101 | 102 | // cannot use alpha.max(beta) in const fn 103 | let b = if b == 0 { 104 | 1 105 | } else if 2 < b { 106 | 2 107 | } else { 108 | b 109 | }; 110 | (a + b - 1) as usize 111 | } 112 | 113 | pub const fn const_str(&self) -> &'static str { 114 | OUTPUT_NAME_STRS[self.get_str_idx()] 115 | } 116 | } 117 | 118 | impl defmt::Format for BufferedOpenDrainKind { 119 | fn format(&self, fmt: defmt::Formatter) { 120 | defmt::write!( 121 | fmt, 122 | "{}", 123 | <&Self as ConstInto<&'static str>>::const_into(self) 124 | ) 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/application/io_card.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © 2023 Jinwoo Park (pmnxis@gmail.com) 3 | * 4 | * SPDX-License-Identifier: MIT OR Apache-2.0 5 | */ 6 | 7 | use card_terminal_adapter::types::IncomeArcadeRequest; 8 | 9 | use super::{DEFAULT_BUSY_ALPHA_TIMING_MS, DEFAULT_VEND_INDICATOR_TIMING_MS}; 10 | use crate::components::eeprom; 11 | use crate::{boards::*, types::player::Player}; 12 | 13 | #[derive(Debug, defmt::Format, Clone, Eq, PartialEq, Ord, PartialOrd)] 14 | pub struct PaymentReceive { 15 | pub origin: Player, 16 | pub recv: IncomeArcadeRequest, 17 | } 18 | 19 | impl From<(IncomeArcadeRequest, Player)> for PaymentReceive { 20 | fn from(value: (IncomeArcadeRequest, Player)) -> Self { 21 | Self { 22 | origin: value.1, 23 | recv: value.0, 24 | } 25 | } 26 | } 27 | 28 | impl From<(Player, IncomeArcadeRequest)> for PaymentReceive { 29 | fn from(value: (Player, IncomeArcadeRequest)) -> Self { 30 | Self { 31 | origin: value.0, 32 | recv: value.1, 33 | } 34 | } 35 | } 36 | 37 | impl From for PaymentReceive { 38 | fn from(value: IncomeArcadeRequest) -> Self { 39 | Self { 40 | origin: Player::default(), 41 | recv: value, 42 | } 43 | } 44 | } 45 | 46 | impl PaymentReceive { 47 | pub async fn apply_output(self, board: &'static Board, override_druation_force: bool) -> Self { 48 | let player = { 49 | defmt::debug!("port 0x{:02X}", self.recv.port); 50 | let temp = (self.recv.port.clamp(1, 4) - 1) & 0x1; 51 | 52 | match temp { 53 | 0 => Player::Player1, 54 | 1 => Player::Player2, 55 | x => { 56 | defmt::warn!("Something is wrong {}", x); 57 | Player::Player1 58 | } 59 | } 60 | }; 61 | 62 | if player != self.origin { 63 | defmt::warn!( 64 | "mismatch player origin, origin : {:?}, player : {:?}", 65 | self.origin, 66 | player 67 | ); 68 | } 69 | 70 | let (vend, busy, led) = player.to_vend_busy_led(board); 71 | 72 | let coin_cnt = self.recv.pulse_count.min(u8::MAX as u16) as u8; 73 | let d = self.recv.pulse_duration; 74 | 75 | if override_druation_force { 76 | vend.tick_tock(coin_cnt).await; 77 | busy.one_shot_high_shared_alpha(coin_cnt, DEFAULT_BUSY_ALPHA_TIMING_MS) 78 | .await; 79 | } else { 80 | vend.alt_tick_tock(coin_cnt, d, d).await; 81 | busy.one_shot_high_mul(coin_cnt, d, d, DEFAULT_BUSY_ALPHA_TIMING_MS) 82 | .await; 83 | } 84 | 85 | match player { 86 | Player::Player1 => { 87 | let count = board 88 | .hardware 89 | .eeprom 90 | .lock_read(eeprom::select::P1_CARD_CNT) 91 | .await; 92 | let new_count = count + coin_cnt as u32; 93 | 94 | board 95 | .hardware 96 | .eeprom 97 | .lock_write(eeprom::select::P1_CARD_CNT, new_count) 98 | .await; 99 | 100 | defmt::debug!("P1_CARD_CNT, {} -> {}", count, new_count); 101 | } 102 | Player::Player2 => { 103 | let count = board 104 | .hardware 105 | .eeprom 106 | .lock_read(eeprom::select::P2_CARD_CNT) 107 | .await; 108 | let new_count = count + coin_cnt as u32; 109 | 110 | board 111 | .hardware 112 | .eeprom 113 | .lock_write(eeprom::select::P2_CARD_CNT, new_count) 114 | .await; 115 | 116 | defmt::debug!("P2_CARD_CNT, {} -> {}", count, new_count); 117 | } 118 | _ => { 119 | defmt::error!("Unrecheable"); 120 | } 121 | } 122 | 123 | led.alt_tick_tock( 124 | coin_cnt, 125 | DEFAULT_VEND_INDICATOR_TIMING_MS, 126 | DEFAULT_VEND_INDICATOR_TIMING_MS, 127 | ) 128 | .await; 129 | 130 | self 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/types/input_port.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © 2023 Jinwoo Park (pmnxis@gmail.com) 3 | * 4 | * SPDX-License-Identifier: MIT OR Apache-2.0 5 | */ 6 | 7 | use num_enum::TryFromPrimitive; 8 | 9 | use crate::semi_layer::buffered_wait::{InputEventKind, RawInputEvent, RawInputPortKind}; 10 | use crate::types::const_convert::*; 11 | use crate::types::player::Player; 12 | 13 | #[allow(dead_code)] 14 | #[repr(u8)] 15 | #[derive(Debug, Clone, Copy, PartialEq, TryFromPrimitive)] 16 | pub enum InputPortKind { 17 | Start1P = 0, 18 | Start2P = 1, 19 | Vend1P = 2, 20 | Vend2P = 3, 21 | Jam1P = 4, 22 | Jam2P = 5, 23 | StartJam1P = 6, 24 | StartJam2P = 7, 25 | Inhibit1P = 8, 26 | Inhibit2P = 9, 27 | SvcButton = 10, 28 | // Ignored signal by filter function 29 | Nothing = 11, 30 | } 31 | 32 | #[cfg(debug_assertions)] 33 | const INPUT_PORT_KIND_STRS: [&str; 12] = [ 34 | "VendIn_1P-Start ", 35 | "VendIn_2P-Start ", 36 | "VendIn_1P-Vend ", 37 | "VendIn_2P-Vend ", 38 | "VendIn_1P-Jam ", 39 | "VendIn_2P-Jam ", 40 | "VendIn_1P-STR/JAM", 41 | "VendIn_2P-STR/JAM", 42 | "HostIn_1P-Inhibit", 43 | "HostIn_2P-Inhibit", 44 | "SVC_Button ", 45 | "Nothing", 46 | ]; 47 | 48 | #[cfg(not(debug_assertions))] 49 | #[rustfmt::skip] 50 | const INPUT_PORT_KIND_STRS: [&str; 12] = [ 51 | "P1V-iSTR", 52 | "P2V-iSTR", 53 | "P1V-iVND", 54 | "P2V-iVND", 55 | "P1V-iJAM", 56 | "P2V-iJAM", 57 | "P1V-iS/J", 58 | "P2V-iS/J", 59 | "P1H-iINH", 60 | "P2H-iINH", 61 | "iSVC_BT ", 62 | "iNothing", 63 | ]; 64 | 65 | // assert_eq!(InputPortKind::count(), INPUT_PORT_KIND_STRS.len()); 66 | 67 | impl defmt::Format for InputPortKind { 68 | fn format(&self, fmt: defmt::Formatter) { 69 | let idx = (*self as u8) as usize; 70 | if idx < INPUT_PORT_KIND_STRS.len() { 71 | defmt::write!(fmt, "InputPortKind::{}", INPUT_PORT_KIND_STRS[idx]) 72 | } else { 73 | defmt::write!(fmt, "Unknown InputPortKind 0x{:02X}", idx as u8) 74 | } 75 | } 76 | } 77 | 78 | impl From for RawInputPortKind { 79 | fn from(value: InputPortKind) -> Self { 80 | value as u8 81 | } 82 | } 83 | 84 | impl const ConstFrom for RawInputPortKind { 85 | fn const_from(value: InputPortKind) -> Self { 86 | value as u8 87 | } 88 | } 89 | 90 | impl const ConstInto for InputPortKind { 91 | fn const_into(self) -> RawInputPortKind { 92 | RawInputPortKind::const_from(self) 93 | } 94 | } 95 | 96 | impl InputPortKind { 97 | pub const fn to_raw_and_const_str(self, player: Player) -> (RawInputPortKind, &'static str) { 98 | let idx = self as u8; 99 | 100 | // PartialEq doesn't support const boundary 101 | // if Player::Undefined == player { 102 | if (Player::Undefined as u8 == player as u8) || (idx == Self::SvcButton as u8) { 103 | let ret: RawInputPortKind = self as u8; 104 | (ret, INPUT_PORT_KIND_STRS[ret as usize]) 105 | } else if Self::Nothing as u8 != idx { 106 | let ret = (idx + player as u8 - 1) as RawInputPortKind; 107 | (ret, INPUT_PORT_KIND_STRS[ret as usize]) 108 | } else { 109 | (self as RawInputPortKind, self.const_str()) 110 | } 111 | } 112 | 113 | pub const fn const_str(self) -> &'static str { 114 | INPUT_PORT_KIND_STRS[self as usize] 115 | } 116 | } 117 | 118 | #[derive(Debug, Clone, Copy)] 119 | pub struct InputEvent { 120 | pub port: InputPortKind, 121 | pub event: InputEventKind, 122 | } 123 | 124 | impl TryFrom for InputEvent { 125 | type Error = num_enum::TryFromPrimitiveError; 126 | fn try_from(value: RawInputEvent) -> Result { 127 | let port: InputPortKind = value.port.try_into()?; 128 | let event: InputEventKind = value.event.into(); 129 | 130 | Ok(Self { port, event }) 131 | } 132 | } 133 | 134 | impl TryFrom<&RawInputEvent> for InputEvent { 135 | type Error = num_enum::TryFromPrimitiveError; 136 | fn try_from(value: &RawInputEvent) -> Result { 137 | let port: InputPortKind = value.port.try_into()?; 138 | let event: InputEventKind = value.event.into(); 139 | 140 | Ok(Self { port, event }) 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /billmock-plug-card/src/lib.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © 2023 Jinwoo Park (pmnxis@gmail.com) 3 | * 4 | * SPDX-License-Identifier: MIT OR Apache-2.0 5 | */ 6 | 7 | #![no_std] 8 | #![feature(const_trait_impl)] 9 | 10 | #[cfg(test)] // for the print out internnal log in test code 11 | extern crate std; 12 | 13 | #[allow(unused)] 14 | mod common; 15 | 16 | use card_terminal_adapter::types::*; 17 | use card_terminal_adapter::*; 18 | 19 | // #[cfg(any(build, test))] 20 | // mod helper; 21 | 22 | pub struct KiccEd785Plug {} 23 | 24 | impl CardTerminalConst for KiccEd785Plug { 25 | fn is_nda() -> bool { 26 | false 27 | } 28 | } 29 | 30 | impl CardTerminalRxParse for KiccEd785Plug { 31 | fn pre_parse_common(&self, _raw: &[u8]) -> Result { 32 | // implement me for actual usage 33 | Err(CardTerminalError::UnsupportedSpec) 34 | } 35 | 36 | fn post_parse_response_sale_slot_info( 37 | &self, 38 | _raw: &[u8], 39 | ) -> Result { 40 | // implement me for actual usage 41 | Err(CardTerminalError::UnsupportedSpec) 42 | } 43 | 44 | fn post_parse_response_terminal_info( 45 | &self, 46 | _raw: &[u8], 47 | _prev_terminal_id: &RawTerminalId, 48 | ) -> Result<(CardTerminalRxCmd, RawTerminalId), CardTerminalError> { 49 | // implement me for actual usage 50 | Err(CardTerminalError::UnsupportedSpec) 51 | } 52 | } 53 | 54 | impl CardTerminalTxGen for KiccEd785Plug { 55 | fn response_ack<'a>(&self, _buffer: &'a mut [u8]) -> &'a [u8] { 56 | // KICC common ACK spec 57 | &common::RAW_DATA_ACK 58 | } 59 | 60 | fn response_nack<'a>(&self, _buffer: &'a mut [u8]) -> &'a [u8] { 61 | // KICC common NACK spec 62 | &common::RAW_DATA_NACK 63 | } 64 | 65 | fn response_device_info<'a, 'b>( 66 | &self, 67 | buffer: &'a mut [u8], 68 | _model_version: &'b [u8; FW_VER_LEN], 69 | _serial_number: &'b [u8; DEV_SN_LEN], 70 | ) -> &'a [u8] { 71 | // implement me for actual usage 72 | &buffer[0..0] 73 | } 74 | 75 | fn alert_coin_paper_acceptor_income<'a>( 76 | &self, 77 | buffer: &'a mut [u8], 78 | _income: RawU24IncomeArcade, 79 | ) -> &'a [u8] { 80 | // implement me for actual usage 81 | &buffer[0..0] 82 | } 83 | 84 | fn push_sale_slot_info<'a>( 85 | &self, 86 | buffer: &'a mut [u8], 87 | _port_backup: &CardReaderPortBackup, 88 | ) -> &'a [u8] { 89 | // implement me for actual usage 90 | &buffer[0..0] 91 | } 92 | 93 | fn push_sale_slot_info_partial_inhibit<'a>( 94 | &self, 95 | buffer: &'a mut [u8], 96 | _port_backup: &CardReaderPortBackup, 97 | ) -> &'a [u8] { 98 | // implement me for actual usage 99 | &buffer[0..0] 100 | } 101 | 102 | fn push_transaction_availability<'a>(&self, buffer: &'a mut [u8], _is_avail: bool) -> &'a [u8] { 103 | // implement me for actual usage 104 | &buffer[0..0] 105 | } 106 | 107 | fn request_sale_slot_info<'a>(&self, buffer: &'a mut [u8]) -> &'a [u8] { 108 | // implement me for actual usage 109 | &buffer[0..0] 110 | } 111 | 112 | fn request_terminal_info<'a>(&self, buffer: &'a mut [u8]) -> &'a [u8] { 113 | // implement me for actual usage 114 | &buffer[0..0] 115 | } 116 | 117 | fn display_rom<'a>( 118 | &self, 119 | buffer: &'a mut [u8], 120 | _git_hash: &'a [u8; GIT_HASH_LEN], 121 | _terminal_id: &[u8; TID_LEN], 122 | _p1_card: u32, 123 | _p2_card: u32, 124 | _p1_coin: u32, 125 | _p2_coin: u32, 126 | ) -> &'a [u8] { 127 | // implement me for actual usage 128 | &buffer[0..0] 129 | } 130 | 131 | fn display_hw_info<'a, 'b>( 132 | &self, 133 | buffer: &'a mut [u8], 134 | _model_version: &'b [u8; FW_VER_LEN], 135 | _serial_number: &'b [u8; DEV_SN_LEN], 136 | _terminal_id: &[u8; TID_LEN], 137 | _hw_boot_cnt: u32, 138 | _uptime_minutes: u32, 139 | ) -> &'a [u8] { 140 | // implement me for actual usage 141 | &buffer[0..0] 142 | } 143 | 144 | fn display_warning<'a>( 145 | &self, 146 | buffer: &'a mut [u8], 147 | _warn_kind: CardTerminalDisplayWarning, 148 | ) -> &'a [u8] { 149 | // implement me for actual usage 150 | &buffer[0..0] 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /book/src/dip_switch.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | # DIP Switch 8 | 9 |
10 | 11 |
12 |
13 | 14 | ## Overview 15 | 16 | | **SW #** | **SW Name** | Anotation | 17 | | :-------: | -----------| --------- | 18 | | `1` | `INHIBIT0` | Inhibit Override - Player 1 | 19 | | `2` | `INHIBIT1` | Inhibit Override - Player 2 | 20 | | `3` | `TIMING0` | Timing Override (50 ms Std) | 21 | | `4` | `TIMING1` | Timing Override (100 ms Std) | 22 | | `5` | `MODE0` | Application mode | 23 | | `6` | `MODE1` | Application mode | 24 | 25 | ## Inhibit override dip switch configuration 26 | 27 | | Inhibit0 (`1`)| Inhibit1P (`2`)| Configuration | 28 | | :-----------: | :-----------: | ------------------------------- | 29 | | `0` | `0` | Normal (No force overriding) | 30 | | `1` | `0` | Override inhibit on 1P as force | 31 | | `0` | `1` | Override inhibit on 2P as force | 32 | | `1` | `1` | Override inhibit globally | 33 | 34 | - By override for each player by dip switch setting, regardless of game I/O signal, 35 | it is forcibly set to inhibit state. 36 | - Even if there is no override in the dip switch settings, 37 | if inhibit is enabled on the host game I/O side, inhibit for each player is activated. 38 | - This Inhibit DIP Switch setting can be used to prohibit currency acquisition 39 | of a device that has under the maintenance in the field engineer. 40 | 41 | ## Timing configuration 42 | 43 | | TIMING0 (`3`) | TIMING1 (`4`) | Configuration | 44 | | :-----------: | :-----------: | ----------------------------- | 45 | | `0` | `0` | Auto | 46 | | `1` | `0` | Force 50mS active low | 47 | | `0` | `1` | Force 100mS active low | 48 | | `1` | `1` | Force 200mS active low | 49 | 50 | - Timing SW `00` (Auto), for the active-low output signal, 51 | the pulse duration provided by serial communication or 52 | the pulse duration measurement value of parallel communication (legacy coin & bill acceptor) 53 | is set separately according to the signal source. 54 | If both are unavailable, the default value (100 mS) will be used. 55 | 56 | - Timing SW `01`, `10`, `11` ignores the pulse duration of all signal sources and 57 | fixes it to one of 50 mS, 100 mS, and 200 mS and outputs it. 58 | 59 | ## Application mode dip switch configuration 60 | 61 | | MODE0 (`5`) | MODE1 (`6`) | Swap status | Special Feature | 62 | | :---------: | :---------: | ------------ | ----------------------------------------------------- | 63 | | `0` | `0` | Start Signal | No, Start signal bypass to host(game pcb) side output | 64 | | `1` | `0` | Start Signal | Yes, Start signal decide vend output direction for payment income from serial communication | 65 | | `0` | `1` | Jam Signal | No, Jam signal bypass to jam(game pcb) side output | 66 | | `1` | `1` | Invalid | `DisplayRom`, disallow any other action | 67 | 68 | - MODE0 (5) : Special feature disable or enable 69 | - MODE1 (6) : Swap `start` and `jam` input signal on vend side, default definition is start. 70 | 71 | - `00` : BypassStart 72 | > Normal mode with bypass start (default value). Start signal bypass to host(game pcb) side output. 73 | 74 | - `01` : StartButtonDecideSerialToVend 75 | > Special mode with start button mocked. 76 | > Start signal decide vend output direction for payment income from serial communication. 77 | 78 | - `10` : BypassJam 79 | > Normal mode with bypass JAM (swapped logically). JAM signal bypass to host(game pcb) side output. 80 | 81 | - `11`: DisplayRom 82 | > ![display rom screen](https://billmock.gpark.biz/images/dip_switch_rom_disp_enus.png) 83 | > - It displays information about BillMock's firmware, the card terminal's TID, and accumulated card and bill (coin) counts. It can be used as an alternative to a magnetic coin meter. 84 | > - Upon exiting this mode, [DisplayHwInfo](./feature_disp_hw_info.md) will display hardware information. 85 | > - When the SVC button is held for more than 10 seconds, the counts are reset to 0 through the [Counter Reset](./feature_counter_reset.md) feature. 86 | > - If the connected card terminal's TID changes, the accumulated card count will be reset to 0. 87 | > For detailed information, please refer to [DisplayRom Detailed Information](./feature_disp_rom.md). 88 | -------------------------------------------------------------------------------- /src/application/pulse_meory_filter.rs: -------------------------------------------------------------------------------- 1 | use card_terminal_adapter::CardTerminalTxCmd; 2 | use embassy_time::{Duration, Instant}; 3 | 4 | use crate::boards::*; 5 | use crate::components::eeprom; 6 | use crate::semi_layer::buffered_wait::InputEventKind; 7 | use crate::types::input_port::{InputEvent, InputPortKind}; 8 | 9 | #[derive(Clone)] 10 | pub(crate) struct PulseMemory { 11 | pub last_tick: Instant, 12 | pub deadline: Duration, 13 | pub count: Option, 14 | } 15 | 16 | pub(crate) struct PulseMemoryFilterMachine { 17 | pub player: [PulseMemory; PLAYER_INDEX_MAX], 18 | } 19 | 20 | impl PulseMemory { 21 | pub fn new() -> Self { 22 | Self { 23 | last_tick: Instant::now(), 24 | deadline: Duration::from_millis(200), 25 | count: None, 26 | } 27 | } 28 | 29 | pub fn is_overtime(&self) -> bool { 30 | (self.last_tick + self.deadline) < Instant::now() 31 | } 32 | 33 | pub fn reset(&mut self) { 34 | self.count = None; 35 | self.deadline = Duration::from_millis(200); // this should be set 36 | } 37 | 38 | pub fn is_stopped(&self) -> bool { 39 | self.count.is_none() 40 | } 41 | 42 | pub fn is_running(&self) -> bool { 43 | self.count.is_some() 44 | } 45 | 46 | pub fn is_running_and_overtime(&self) -> bool { 47 | self.is_running() && self.is_overtime() 48 | } 49 | 50 | pub fn mark(&mut self, timing_in_ms: u16) { 51 | let was_stopped = self.is_stopped(); 52 | self.count = Some(self.count.unwrap_or_default() + 1); 53 | 54 | self.last_tick = Instant::now(); 55 | if was_stopped { 56 | let deadline_in_ms = ((timing_in_ms * 3) / 2).min(1200); 57 | self.deadline = Duration::from_millis(deadline_in_ms.into()); 58 | } 59 | } 60 | } 61 | 62 | impl PulseMemoryFilterMachine { 63 | pub fn new() -> Self { 64 | let new = PulseMemory::new(); 65 | Self { 66 | player: [new.clone(), new], 67 | } 68 | } 69 | 70 | #[allow(unused)] // this is just example of usage. 71 | pub fn detect_signal(&mut self, input: &InputEvent) { 72 | // should I detect signal first? 73 | if let Some((index, timing_in_ms)) = match input { 74 | InputEvent { 75 | port: InputPortKind::Vend1P, 76 | event: InputEventKind::LongPressed(time_in_10ms), 77 | } => Some((PLAYER_1_INDEX, (*time_in_10ms as u16) * 10)), 78 | InputEvent { 79 | port: InputPortKind::Vend2P, 80 | event: InputEventKind::LongPressed(time_in_10ms), 81 | } => Some((PLAYER_2_INDEX, (*time_in_10ms as u16) * 10)), 82 | _ => None, 83 | } { 84 | let mut target = &mut self.player[index]; 85 | target.mark(timing_in_ms); 86 | } 87 | } 88 | 89 | pub async fn report_when_expired(&mut self, board: &'static Board) { 90 | for player_index in [PLAYER_1_INDEX, PLAYER_2_INDEX] { 91 | if self.player[player_index].is_running_and_overtime() { 92 | if let Some(assume_report) = board 93 | .hardware 94 | .eeprom 95 | .lock_read(eeprom::select::CARD_PORT_BACKUP) 96 | .await 97 | .guess_raw_income_by_player(1 + player_index as u8) 98 | { 99 | let actual = self.player[player_index].count.unwrap_or_default(); 100 | let expected = assume_report.get_pulse_count(); 101 | if actual != expected { 102 | defmt::warn!("Player {} - CashReceipt clock mismatch but ignored, actual : {}, expected : {}", player_index+1, actual, expected); 103 | } else { 104 | defmt::info!( 105 | "Player {} - CashReceipt clock actual : {}", 106 | player_index + 1, 107 | actual 108 | ); 109 | } 110 | 111 | board 112 | .hardware 113 | .card_reader 114 | .send(CardTerminalTxCmd::PushCoinPaperAcceptorIncome( 115 | assume_report.clone(), 116 | )) 117 | .await; 118 | } else { 119 | defmt::info!( 120 | "Player {} - cash receipt could not be requested", 121 | player_index + 1 122 | ); 123 | } 124 | 125 | self.player[player_index].reset(); 126 | } 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | # `billmock-app-rs` 8 | 9 | Rust Embedded firmware using **rust embedded-hal**[1](#footnote_1) and **embassy-rs**[2](#footnote_1) on **STM32G030C8**[3](#footnote_1) MCU for Billmock product. 10 | 11 | Used rust experimentally. 12 | 13 | This repository is aiming three goal. 14 | One for development of production firmware and second is making a proof of concept that rust embedded is usable for actual embedded production. And last goal is setting some example about production-rust-embedded-code. 15 | 16 | **This project is currently under development, with ongoing QA testing and some optimization remaining.** 17 | 18 | ## Billmock 19 | Detail documentation is here [BillMock Manual](https://billmock.pmnxis.net/) 20 | 21 | It is hardware and software for the purpose of converting I/O signals related to money payment of arcade game machines for compatibility. 22 | 23 | This project began development at the request of **GPARK Co., Ltd**[4](#footnote_1) and was designed and developed for the using credit card readers in arcade game machine and compatibility of existing payment systems (open-drain-based), and is open to the public except for code in the NDA area. 24 | The project has been granted an open source disclosure except of NDA era. 25 | 26 | ## Target Hardware 27 | Based on BillMock-HW 0.5 mini, 0.4 mini and 0.4.
28 | 0.2 and 0.3 HW bring-up codes are still left for recyle the old PCB. 29 | - 0.2 HW has different gpio configuration compare to latest boards. 30 | - 0.3 HW has minor bugs, floating on VendSide-Inhibit and missing net route on VendSide-1P-StartJam. 31 | - 0.4 HW fixed 0.3 HW bugs. 32 | - 0.4 HW mini reduced BOM for mass-manufacturing 33 | - 0.5 HW mini added tact switch for SVC mode call 34 | 35 | Current default HW is `0.5 mini`. If need to use old 0.4 or 0.4 mini, following below command lines 36 | ```sh 37 | cargo build --features hw_0v4 --no-default-features --release 38 | cargo build --features hw_mini_0v4 --no-default-features --release 39 | ``` 40 | 41 | ### Target hardware image 42 | ![Actual BillMock PCB 0v5](https://billmock.gpark.biz/images/BillMockPCB_0v5_mini.jpg) 43 | The hardware revision currently adopted for final mass production is 0.5-MINI, and the software development is also progressing according to this version. 44 | 45 | ![Actual BillMock PCB 0v4](https://billmock.gpark.biz/images/BillMockPCB_0v4.jpg) 46 | Old revisions. 47 | 48 | ### Hardware design 49 | BillMock hardware schematic repository (only pdf) 50 | https://github.com/pmnxis/BillMock-HW-RELEASE 51 | 52 | The schematic printed in PDF is distributed under CC BY-SA 3.0, but the actual Gerber files and project files are private. 53 | 54 | #### v 0.5 Mini (2023-10-24) 55 | [BillMock-Mini-HW-0v5.pdf](https://github.com/pmnxis/BillMock-HW-RELEASE/blob/master/sch/BillMock-Mini-HW-0v5.pdf) 56 | 57 | #### v 0.4 Mini (2023-09-12 or 2023-09-13) 58 | [BillMock-Mini-HW-0v4.pdf](https://github.com/pmnxis/BillMock-HW-RELEASE/blob/master/sch/BillMock-Mini-HW-0v4.pdf) 59 | 60 | #### v 0.4 (2023-09-08) 61 | [BillMock-HW-0v4.pdf](https://github.com/pmnxis/BillMock-HW-RELEASE/blob/master/sch/BillMock-HW-0v4.pdf) 62 | 63 | #### ~~v 0.3 (2023-08-11)~~ - DEPRECATED 64 | ~~[BillMock-HW-0v3.pdf](https://github.com/pmnxis/BillMock-HW-RELEASE/blob/master/sch/BillMock-HW-0v3.pdf)~~ 65 | 66 | #### ~~v 0.2 (2023-06-13)~~ - DEPRECATED 67 | ~~[BillMock-HW-0v2.pdf](https://github.com/pmnxis/BillMock-HW-RELEASE/blob/master/sch/BillMock-HW-0v2.pdf)~~ 68 | 69 | 70 | ## Feature diagram 71 | ![BillMock feature diagram](https://billmock.gpark.biz/images/billmock_logic_diagram.png) 72 | 73 | ## Dependencies 74 | See details here [dependencies](docs/dependencies.md) 75 | 76 | ### NDA Dependencies 77 | - [Dependency Injection for card reader](https://billmock.pmnxis.net/dev/dependency_injection.html) 78 | - [Detail stories](docs/SerialDevice.md) 79 | 80 | ## License 81 | This program and the accompanying materials are made available under the terms 82 | of the Apache Software License 2.0 which is available at 83 | https://www.apache.org/licenses/LICENSE-2.0, or the MIT license which is 84 | available at https://opensource.org/licenses/MIT 85 | 86 | Also all of codes are based one MIT or Apache Software License 2.0. But some common *.toml files are based on CC0-1.0 license. (Example Cargo.toml) 87 | 88 | ## Footnote 89 | 1 `rust embedded-hal` is hardware abstraction layer written in rust
90 | ( https://github.com/rust-embedded/embedded-hal )

91 | 92 | 2 `embassy-rs` is rust embedded framework
93 | ( https://github.com/embassy-rs/embassy )

94 | 95 | 3 `STM32G030C8` is STMicroelectronics' MCU with ARM-Cortex M0+ , 64KiB Flash and 8KiB SRAM.
96 | ( https://www.st.com/en/microcontrollers-microprocessors/stm32g030c8.html )

97 | 98 | 4: `GPARK Co., Ltd.` is a company in South Korea that operates the arcade game industry. 99 | -------------------------------------------------------------------------------- /book/src/port_vend_side.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | # Vend side port map 8 | 9 | ## Vend Side Quick Terminal 10 | 11 | 12 | 16 | 36 |
13 | 14 | ![J9](https://billmock.gpark.biz/images/pcb_0v4_port/J9.png) 15 | 17 | 18 | | | | 19 | | -------------- | -------------- | 20 | | Designator | J9 | 21 | | | Existing coin and bill acceptor ports (terminal types) | 22 | | Connector | 141R-2.54-8P | 23 | 24 | | **Pin #** | **Pin Name** | Annotation | 25 | | :-------: | ------------ | ---------------------------------------- | 26 | | `1` | `GND` | Bill acceptor - Pole Power (Input/Output) | 27 | | `2` | `GND` | Bill acceptor - Pole Power (Input/Output) | 28 | | `3` | `12V` | Bill acceptor + Pole Power (Input/Output) | 29 | | `4` | `INHIBIT` | Bill acceptor Inhibit Deactivate Output Signal | 30 | | `5` | `JAM` | Bill acceptor 1P Coin Insertion Signal | 31 | | `6` | `VEND` | Bill acceptor 2P Coin Insertion Signal | 32 | | `7` | `START1` | Start Button 1 Input Signal | 33 | | `8` | `START2` | Start Button 2 Input Signal | 34 | 35 |
37 | 38 | - Pins are counted from the left. 39 | - You can also input power directly into BillMock-HW through `12V` and `GND` pins. 40 | - While it's more convenient to strip tde insulation from tde cable in tde middle and tden connect it to tde terminal, 41 | - It's recommended, whenever possible, to use a cable type that comes pre-equipped for the connection. 42 | - START1/2 can be changed to JAM based on DIP switch settings 43 | 44 | ------------ 45 | 46 | ## Vend Side Player 1 Port (left) 47 | 48 | 49 | 53 | 77 |
50 | 51 | ![J7](https://billmock.gpark.biz/images/pcb_0v4_port/J7.png) 52 | 54 | 55 | | | | 56 | | -------------- | -------------- | 57 | | Designator | J7 | 58 | | | Player 1 side existing
coin and bill acceptor port | 59 | | Connector | Molex 53014-10xx | 60 | | Housing | Molex 51004-10xx | 61 | | Crimp | Molex 50011 | 62 | 63 | | **Pin #** | **Pin Name** | Annotation | 64 | | :-------: | ------------ | ---------------------------------------------- | 65 | | `1` | N/C | Not Connected | 66 | | `2` | `VEND` | Bill acceptor Coin Insertion Signal | 67 | | `3` | N/C | Not Connected | 68 | | `4` | `START` | Start Button Input Signal | 69 | | `5` | `INHIBIT` | Bill acceptor Inhibit (Deactivate) Output Signal | 70 | | `6` | `GND` | Bill acceptor - Pole Power (Input/Output) | 71 | | `7` | N/C | Not Connected | 72 | | `8` | `12V` | Bill acceptor + Pole Power (Input/Output) | 73 | | `9` | `12V` | Bill acceptor + Pole Power (Input/Output) | 74 | | `10` | `GND` | Bill acceptor - Pole Power (Input/Output) | 75 | 76 |
78 | 79 | - Pins are counted from the left. 80 | - START can be changed to JAM based on DIP switch settings 81 | 82 | ------------ 83 | 84 | ## Vend Side Player 2 Port (right) 85 | 86 | 87 | 91 | 115 |
88 | 89 | ![J8](https://billmock.gpark.biz/images/pcb_0v4_port/J8.png) 90 | 92 | 93 | | | | 94 | | -------------- | -------------- | 95 | | Designator | J8 | 96 | | | Player 2 side existing
coin and bill acceptor port | 97 | | Connector | Molex 53014-10xx | 98 | | Housing | Molex 51004-10xx | 99 | | Crimp | Molex 50011 | 100 | 101 | | **Pin #** | **Pin Name** | Annotation | 102 | | :-------: | ------------ | ---------------------------------------------- | 103 | | `1` | N/C | Not Connected | 104 | | `2` | `VEND` | Bill acceptor Coin Insertion Signal | 105 | | `3` | N/C | Not Connected | 106 | | `4` | `START` | Start Button Input Signal | 107 | | `5` | `INHIBIT` | Bill acceptor Inhibit (Deactivate) Output Signal | 108 | | `6` | `GND` | Bill acceptor - Pole Power (Input/Output) | 109 | | `7` | N/C | Not Connected | 110 | | `8` | `12V` | Bill acceptor + Pole Power (Input/Output) | 111 | | `9` | `12V` | Bill acceptor + Pole Power (Input/Output) | 112 | | `10` | `GND` | Bill acceptor - Pole Power (Input/Output) | 113 | 114 |
116 | 117 | - Pins are counted from the left. 118 | - START can be changed to JAM based on DIP switch settings -------------------------------------------------------------------------------- /book/src/dev/develop_environment.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | # Develop Environment 8 | 9 | _※ It might available with Windows, but in this document only consider Debian-Linux._ 10 | 11 | ### apply USB (stlink-v2/3) permission rule to ignore root access 12 | ```sh 13 | # refer https://calinradoni.github.io/pages/200616-non-root-access-usb.html 14 | sudo tee /etc/udev/rules.d/70-st-link.rules > /dev/null <<'EOF' 15 | # ST-LINK V2 16 | SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="3748", GROUP="plugdev", MODE="660", TAG+="uaccess", SYMLINK+="stlinkv2_%n" 17 | 18 | # ST-LINK V2.1 19 | SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="374b", GROUP="plugdev", MODE="660", TAG+="uaccess", SYMLINK+="stlinkv2-1_%n" 20 | SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="3752", GROUP="plugdev", MODE="660", TAG+="uaccess", SYMLINK+="stlinkv2-1_%n" 21 | 22 | # ST-LINK V3 23 | SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="374d", GROUP="plugdev", MODE="660", TAG+="uaccess", SYMLINK+="stlinkv3loader_%n" 24 | SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="374e", GROUP="plugdev", MODE="660", TAG+="uaccess", SYMLINK+="stlinkv3_%n" 25 | SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="374f", GROUP="plugdev", MODE="660", TAG+="uaccess", SYMLINK+="stlinkv3_%n" 26 | SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="3753", GROUP="plugdev", MODE="660", TAG+="uaccess", SYMLINK+="stlinkv3_%n" 27 | EOF 28 | ``` 29 | 30 | ### apply rules and reboot linux 31 | ```sh 32 | sudo usermod -a -G plugdev $USER 33 | sudo udevadm control --reload-rules 34 | sudo udevadm trigger 35 | sudo reboot 36 | ``` 37 | 38 | ### Necessary apt package for rust embedded 39 | ```sh 40 | # necessary for basic software development environment 41 | sudo apt install curl git build-essential -y 42 | # build.rs uses libgit2-sys to get commit hash 43 | sudo apt install pkg-config libssl-dev -y 44 | # for knurling-rs/probe-run 45 | sudo apt install libusb-1.0-0-dev libudev-dev -y 46 | 47 | # Install rustc/rustup/cargo for rust development environment 48 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh 49 | 50 | # Install probe-run for debug/flash firmware binary on target board. 51 | cargo install probe-run 52 | 53 | # Install cargo-binutils for analyze mem/flash usage. 54 | cargo install cargo-binutils 55 | rustup component add llvm-tools-preview 56 | ``` 57 | 58 | ### Build 59 | ```sh 60 | cargo build 61 | ``` 62 | 63 | ```sh 64 | pmnxis@lmabdaDeb  ~/Develop/billmock-app-rs   master  cargo build 65 | info: syncing channel updates for 'nightly-2023-08-24-x86_64-unknown-linux-gnu' 66 | info: latest update on 2023-08-24, rust version 1.74.0-nightly (249595b75 2023-08-23) 67 | info: downloading component 'cargo' 68 | info: downloading component 'clippy' 69 | info: downloading component 'llvm-tools' 70 | info: downloading component 'rust-docs' 71 | info: downloading component 'rust-src' 72 | info: downloading component 'rust-std' for 'thumbv6m-none-eabi' 73 | info: downloading component 'rust-std' 74 | info: downloading component 'rustc' 75 | info: downloading component 'rustfmt' 76 | info: installing component 'cargo' 77 | info: installing component 'clippy' 78 | info: installing component 'llvm-tools' 79 | info: installing component 'rust-docs' 80 | info: installing component 'rust-src' 81 | info: installing component 'rust-std' for 'thumbv6m-none-eabi' 82 | info: installing component 'rust-std' 83 | info: installing component 'rustc' 84 | info: installing component 'rustfmt' 85 | Updating crates.io index 86 | Updating git repository `https://github.com/embassy-rs/embassy.git` 87 | ``` 88 | 89 | ### Cargo Run 90 | ```sh 91 | pmnxis@lmabdaDeb  ~/Develop/billmock-app-rs   master  cargo run 92 | Finished dev [optimized + debuginfo] target(s) in 0.06s 93 | Running `probe-run --chip STM32G030C8Tx --log-format '{t} [{L}][ {f}:{l} ] {s}' target/thumbv6m-none-eabi/debug/billmock-app-rs` 94 | (HOST) INFO flashing program (45 pages / 45.00 KiB) 95 | (HOST) INFO success! 96 | ──────────────────────────────────────────────────────────────────────────────── 97 | 0.000000 [DEBUG][ fmt.rs:130 ] rcc: Clocks { sys: Hertz(16000000), apb1: Hertz(16000000), apb1_tim: Hertz(16000000), ahb1: Hertz(16000000) } 98 | +-----------------------------------------------------------+ 99 | Firmware Ver : billmock-app-rs 0.1.2 100 | Git Hash : bf976acd38633f7204e9423def8b5b062e0a0ad3 101 | Git Datetime : 2023-09-17 20:55:10 +0900 | bf976ac 102 | +-----------------------------------------------------------+ 103 | 0.004272 [TRACE][ fmt.rs:117 ] USART: presc=1, div=0x0000008b (mantissa = 8, fraction = 11) 104 | 0.004638 [TRACE][ fmt.rs:117 ] Using 16 bit oversampling, desired baudrate: 115200, actual baudrate: 115107 105 | 1.006286 [WARN ][ serial_device.rs:156 ] The module use a example library. It may not work in real fields. 106 | OUT[ LED2-Indicator] : Low 107 | OUT[ LED1-Indicator] : Low 108 | ``` 109 | -------------------------------------------------------------------------------- /src/semi_layer/buffered_wait.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © 2023 Jinwoo Park (pmnxis@gmail.com) 3 | * 4 | * SPDX-License-Identifier: MIT OR Apache-2.0 5 | */ 6 | 7 | use core::cell::UnsafeCell; 8 | 9 | use embassy_stm32::exti::ExtiInput; 10 | use embassy_stm32::gpio::AnyPin; 11 | use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; 12 | use embassy_sync::channel::Channel; 13 | use embassy_time::Instant; 14 | 15 | pub const MPSC_WAIT_INPUT_EVENT_CH_SIZE: usize = 32; 16 | 17 | pub type RawInputPortKind = u8; 18 | 19 | // 8bit-sized enum 20 | #[derive(Debug, Clone, Copy)] 21 | pub enum InputEventKind { 22 | /// Released signal (active High) 23 | Released, 24 | /// Pressed signal (active Low) 25 | Pressed, 26 | /// Long press signal with time 27 | /// Internal time max value is 0x7F (127), the value * 10 is pressed time in Msecs 28 | LongPressed(u8), 29 | } 30 | 31 | impl defmt::Format for InputEventKind { 32 | fn format(&self, fmt: defmt::Formatter) { 33 | match self { 34 | InputEventKind::Released => defmt::write!(fmt, "Released"), 35 | InputEventKind::Pressed => defmt::write!(fmt, "Pressed"), 36 | InputEventKind::LongPressed(x) => defmt::write!(fmt, "LongPressed({})", x), 37 | } 38 | } 39 | } 40 | 41 | pub struct RawInputEvent { 42 | pub port: RawInputPortKind, 43 | pub event: RawInputEventKind, 44 | } 45 | 46 | pub type InputEventChannel = 47 | Channel; 48 | 49 | pub type RawInputEventKind = u8; 50 | const TINY_LONG_PRESS_MAX: u8 = (0x1 << 7) - 1; 51 | 52 | impl From for InputEventKind { 53 | fn from(value: RawInputEventKind) -> Self { 54 | if value == 0 { 55 | Self::Released 56 | } else { 57 | match value { 58 | 0b1000_0000 => Self::Pressed, 59 | x => Self::LongPressed(x & 0b0111_1111), 60 | } 61 | } 62 | } 63 | } 64 | 65 | impl From for RawInputEventKind { 66 | fn from(value: InputEventKind) -> Self { 67 | match value { 68 | InputEventKind::Released => 0x00, 69 | InputEventKind::Pressed => 0x1 << 7, 70 | InputEventKind::LongPressed(x) => (0x1 << 7) | x.clamp(1, TINY_LONG_PRESS_MAX), 71 | } 72 | } 73 | } 74 | 75 | /// Internal PullUp + 4050 + OpenDrain outside (NMOS or ULN2803) 76 | pub struct BufferedWait { 77 | wait: UnsafeCell>, 78 | channel: &'static InputEventChannel, 79 | port: RawInputPortKind, 80 | #[cfg(debug_assertions)] 81 | debug_name: &'static str, 82 | } 83 | 84 | #[allow(unused)] 85 | impl BufferedWait { 86 | pub const fn new( 87 | wait: ExtiInput<'static, AnyPin>, 88 | channel: &'static InputEventChannel, 89 | port: RawInputPortKind, 90 | debug_name: &'static str, 91 | ) -> BufferedWait { 92 | Self { 93 | wait: UnsafeCell::new(wait), 94 | channel, 95 | port, 96 | 97 | #[cfg(debug_assertions)] 98 | debug_name, 99 | } 100 | } 101 | 102 | async fn send(&self, event: InputEventKind) { 103 | self.channel 104 | .send(RawInputEvent { 105 | port: self.port, 106 | event: event.into(), 107 | }) 108 | .await; 109 | } 110 | 111 | pub async fn run(&self) -> ! { 112 | // wait high for fit ot initial state. 113 | let wait = unsafe { &mut *self.wait.get() }; 114 | wait.wait_for_high().await; 115 | 116 | #[cfg(debug_assertions)] 117 | defmt::println!("IN [{} ] : High", self.debug_name); 118 | 119 | loop { 120 | // detect low signal (active low) 121 | wait.wait_for_low().await; 122 | let entered_time = Instant::now(); 123 | 124 | #[cfg(debug_assertions)] 125 | defmt::println!("IN [{} ] : Low", self.debug_name); 126 | 127 | self.send(InputEventKind::Pressed).await; 128 | 129 | // detect high signal (active high) 130 | wait.wait_for_high().await; 131 | let hold_time = Instant::now() - entered_time; 132 | 133 | #[cfg(debug_assertions)] 134 | defmt::println!( 135 | "IN [{} ] : High, duration : {=u64:us}", 136 | self.debug_name, 137 | hold_time.as_micros() 138 | ); 139 | 140 | match (hold_time.as_millis().min(TINY_LONG_PRESS_MAX as u64 * 10) / 10) 141 | as RawInputEventKind 142 | { 143 | 0 => { /* too short time pressed */ } 144 | x => { 145 | self.send(InputEventKind::LongPressed(x)).await; 146 | } 147 | } 148 | self.send(InputEventKind::Released).await; 149 | } 150 | } 151 | } 152 | 153 | // in HW v0.4 pool usage would be 6. 154 | // single task pool consume 88 bytes 155 | #[cfg(not(feature = "svc_button"))] 156 | #[embassy_executor::task(pool_size = 6)] 157 | pub async fn buffered_wait_spawn(instance: &'static BufferedWait) { 158 | instance.run().await 159 | } 160 | 161 | // in HW v0.5 pool usage would be 7. (+ SVC_Button) 162 | // single task pool consume 88 bytes 163 | #[cfg(feature = "svc_button")] 164 | #[embassy_executor::task(pool_size = 7)] 165 | pub async fn buffered_wait_spawn(instance: &'static BufferedWait) { 166 | instance.run().await 167 | } 168 | -------------------------------------------------------------------------------- /book/src/port_host_side.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | # Host side port map 8 | 9 | ## Host Side Quick Terminal 10 | 11 | 12 | 16 | 36 |
13 | 14 | ![J6](https://billmock.gpark.biz/images/pcb_0v4_port/J6.png) 15 | 17 | 18 | | | | 19 | | -------------- | -------------- | 20 | | Designator | J6 | 21 | | | terminal that emulates towards the GAME IO side | 22 | | Connector | 141R-2.54-8P | 23 | 24 | | **Pin #** | **Pin Name** | Annotation | 25 | | :-------: | -------------- | --------------------------------------------------------- | 26 | | `1` | `GND` | Product - Pole Power Input (Add' Output) | 27 | | `2` | `GND` | Product - Pole Power Input (add' Output) | 28 | | `3` | `12V` | Product + Pole Power Input | 29 | | `4` | `V1-INHIBIT` | Inhibit Input Signal from 1P GAME I/O | 30 | | `5` | `V1-VEND` | Emulated Bill acceptor 1P Coin Insertion Output Signal | 31 | | `6` | `V2-VEND` | Emulated Bill acceptor 2P Coin Insertion Output Signal | 32 | | `7` | `V1-START` | Emulated 1P Start Button Output Signal | 33 | | `8` | `V2-START` | Emulated 2P Start Button Output Signal | 34 | 35 |
37 | 38 | - Pins are counted from the left. 39 | - You can also input power directly into BillMock-HW through `12V` and `GND` pins. 40 | - While it's more convenient to strip tde insulation from tde cable in tde middle and tden connect it to tde terminal, 41 | - It's recommended, whenever possible, to use a cable type that comes pre-equipped for the connection. 42 | 43 | ------------ 44 | 45 | ## Host Side Player 1 Port (left) 46 | 47 | 48 | 52 | 74 |
49 | 50 | ![J5](https://billmock.gpark.biz/images/pcb_0v4_port/J5.png) 51 | 53 | 54 | | | | 55 | | -------------- | -------------- | 56 | | Designator | J5 | 57 | | | Emulated Player 1 side connector towards the GAME IO side | 58 | | Connector | 141R-2.54-8P | 59 | 60 | | **Pin #** | **Pin Name** | Annotation | 61 | | :-------: | -------------- | ------------------------------------------------------ | 62 | | `1` | `V1-BUSY` | Emulated Busy state Output Signal | 63 | | `2` | `V1-VEND` | Emulated Bill acceptor 1P Coin Insertion Output Signal | 64 | | `3` | `V1-JAM` | Emulated JAM Output Signal | 65 | | `4` | `V1-START` | Emulated 1P Start Button Output Signal | 66 | | `5` | `V1-INHIBIT` | Inhibit Input Signal from 1P GAME I/O | 67 | | `6` | N/C | Not Connected | 68 | | `7` | N/C | Not Connected | 69 | | `8` | `12V` | Product + Pole Power Input | 70 | | `9` | `12V` | Product + Pole Power Input | 71 | | `10` | `GND` | Product - Pole Power Input | 72 | 73 |
75 | 76 | - Pins are counted from the left. 77 | - You can also input power directly into BillMock-HW through `12V` and `GND` pins. 78 | - The power pins from this port cannot be used for power output. When power input to this port is blocked, reverse voltage does not flow. 79 | - The "Busy" output signal remains active low from the moment a payment signal is received from the credit card or when the VEND input signal goes active low until the VEND output signal toggles and completes. 80 | 81 | ------------ 82 | 83 | ## Host Side Player 2 Port (right) 84 | 85 | 86 | 90 | 112 |
87 | 88 | ![J4](https://billmock.gpark.biz/images/pcb_0v4_port/J4.png) 89 | 91 | 92 | | | | 93 | | -------------- | -------------- | 94 | | Designator | J4 | 95 | | | Emulated Player 2 side connector towards the GAME IO side | 96 | | Connector | 141R-2.54-8P | 97 | 98 | | **Pin #** | **Pin Name** | Annotation | 99 | | :-------: | -------------- | ------------------------------------------------------ | 100 | | `1` | `V1-BUSY` | Emulated Busy state Output Signal | 101 | | `2` | `V1-VEND` | Emulated Bill acceptor 2P Coin Insertion Output Signal | 102 | | `3` | `V1-JAM` | Emulated JAM Output Signal | 103 | | `4` | `V1-START` | Emulated 2P Start Button Output Signal | 104 | | `5` | `V1-INHIBIT` | Inhibit Input Signal from 2P GAME I/O | 105 | | `6` | N/C | Not Connected | 106 | | `7` | N/C | Not Connected | 107 | | `8` | `12V` | Product + Pole Power Input | 108 | | `9` | `12V` | Product + Pole Power Input | 109 | | `10` | `GND` | Product - Pole Power Input | 110 | 111 |
113 | 114 | - Pins are counted from the left. 115 | - You can also input power directly into BillMock-HW through `12V` and `GND` pins. 116 | - The power pins from this port cannot be used for power output. When power input to this port is blocked, reverse voltage does not flow. 117 | - The "Busy" output signal remains active low from the moment a payment signal is received from the credit card or when the VEND input signal goes active low until the VEND output signal toggles and completes. 118 | -------------------------------------------------------------------------------- /src/application/io_remap.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © 2023 Jinwoo Park (pmnxis@gmail.com) 3 | * 4 | * SPDX-License-Identifier: MIT OR Apache-2.0 5 | */ 6 | 7 | use super::io_bypass::io_bypass; 8 | use super::pulse_meory_filter::PulseMemoryFilterMachine; 9 | use crate::boards::*; 10 | use crate::components::eeprom; 11 | use crate::semi_layer::buffered_wait::InputEventKind; 12 | use crate::types::input_port::{InputEvent, InputPortKind}; 13 | use crate::types::player::Player; 14 | 15 | #[allow(dead_code)] 16 | impl InputEvent { 17 | pub fn replace(&self, port: (InputPortKind, InputPortKind)) -> Self { 18 | if self.port == port.0 { 19 | Self { 20 | port: port.1, 21 | event: self.event, 22 | } 23 | } else { 24 | *self 25 | } 26 | } 27 | 28 | pub fn replace_arr(&self, ports: &[(InputPortKind, InputPortKind)]) -> Self { 29 | for port in ports { 30 | if self.port == port.0 { 31 | return Self { 32 | port: port.1, 33 | event: self.event, 34 | }; 35 | } 36 | } 37 | *self 38 | } 39 | 40 | pub fn ignore(&self, port: InputPortKind) -> Self { 41 | // todo! - bitfield based filter system efficient instruction usage 42 | if self.port == port { 43 | Self { 44 | port: InputPortKind::Nothing, 45 | event: self.event, 46 | } 47 | } else { 48 | *self 49 | } 50 | } 51 | 52 | pub fn ignore_arr(&self, ports: &[InputPortKind]) -> Self { 53 | // todo! - bitfield based filter system efficient instruction usage 54 | for port in ports { 55 | if self.port == *port { 56 | return Self { 57 | port: InputPortKind::Nothing, 58 | event: self.event, 59 | }; 60 | } 61 | } 62 | *self 63 | } 64 | 65 | pub fn allow(&self, port: InputPortKind) -> Self { 66 | // todo! - bitfield based filter system efficient instruction usage 67 | if self.port == port { 68 | *self 69 | } else { 70 | Self { 71 | port: InputPortKind::Nothing, 72 | event: self.event, 73 | } 74 | } 75 | } 76 | 77 | pub fn allow_arr(&self, ports: &[InputPortKind]) -> Self { 78 | // todo! - bitfield based filter system efficient instruction usage 79 | for port in ports { 80 | if self.port == *port { 81 | return *self; 82 | } 83 | } 84 | Self { 85 | port: InputPortKind::Nothing, 86 | event: self.event, 87 | } 88 | } 89 | 90 | pub fn flip_player(&self) -> Self { 91 | Self { 92 | port: match self.port { 93 | InputPortKind::Inhibit1P => InputPortKind::Inhibit2P, 94 | InputPortKind::Inhibit2P => InputPortKind::Inhibit1P, 95 | InputPortKind::Start1P => InputPortKind::Start2P, 96 | InputPortKind::Start2P => InputPortKind::Start1P, 97 | InputPortKind::Jam1P => InputPortKind::Jam2P, 98 | InputPortKind::Jam2P => InputPortKind::Jam1P, 99 | InputPortKind::StartJam1P => InputPortKind::StartJam2P, 100 | InputPortKind::StartJam2P => InputPortKind::StartJam1P, 101 | InputPortKind::Vend2P => InputPortKind::Vend1P, 102 | InputPortKind::Vend1P => InputPortKind::Vend2P, 103 | InputPortKind::SvcButton => InputPortKind::SvcButton, 104 | InputPortKind::Nothing => InputPortKind::Nothing, 105 | }, 106 | event: self.event, 107 | } 108 | } 109 | 110 | pub fn ignore_player(&self, player: Player) -> Self { 111 | match (self.port, player) { 112 | ( 113 | InputPortKind::Inhibit1P 114 | | InputPortKind::Jam1P 115 | | InputPortKind::Start1P 116 | | InputPortKind::Vend1P, 117 | Player::Player1, 118 | ) => *self, 119 | ( 120 | InputPortKind::Inhibit2P 121 | | InputPortKind::Jam2P 122 | | InputPortKind::Start2P 123 | | InputPortKind::Vend2P, 124 | Player::Player2, 125 | ) => *self, 126 | _ => Self { 127 | port: InputPortKind::Nothing, 128 | event: self.event, 129 | }, 130 | } 131 | } 132 | 133 | pub async fn apply_output( 134 | &self, 135 | board: &'static Board, 136 | filter_state: &mut PulseMemoryFilterMachine, 137 | override_druation_force: bool, 138 | ) -> Self { 139 | if let Some((player_index, rom_sel, time_in_10ms)) = match (self.port, self.event) { 140 | (InputPortKind::Vend1P, InputEventKind::LongPressed(time_in_10ms)) => Some(( 141 | PLAYER_1_INDEX as u8, 142 | eeprom::select::P1_COIN_CNT, 143 | time_in_10ms, 144 | )), 145 | (InputPortKind::Vend2P, InputEventKind::LongPressed(time_in_10ms)) => Some(( 146 | PLAYER_2_INDEX as u8, 147 | eeprom::select::P2_COIN_CNT, 148 | time_in_10ms, 149 | )), 150 | _ => None, 151 | } { 152 | let count = board.hardware.eeprom.lock_read(rom_sel).await; 153 | let new_count = count + 1; 154 | 155 | board.hardware.eeprom.lock_write(rom_sel, new_count).await; 156 | 157 | let timing_in_ms = (time_in_10ms as u16) * 10; 158 | 159 | filter_state.player[player_index as usize].mark(timing_in_ms); 160 | } 161 | 162 | if self.port != InputPortKind::Nothing { 163 | io_bypass(board, self, override_druation_force).await; 164 | } 165 | *self 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/application/mutual_inhibit.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © 2023 Jinwoo Park (pmnxis@gmail.com) 3 | * 4 | * SPDX-License-Identifier: MIT OR Apache-2.0 5 | */ 6 | 7 | use bit_field::BitField; 8 | 9 | use crate::boards::{Board, PLAYER_1_INDEX, PLAYER_2_INDEX}; 10 | use crate::semi_layer::buffered_wait::InputEventKind; 11 | use crate::types::dip_switch_config::InhibitOverride; 12 | use crate::types::input_port::{InputEvent, InputPortKind}; 13 | use crate::types::player::Player; 14 | 15 | /// Mutual Inhibit module for resolve complex inhibit input / output source. 16 | #[derive(Clone, Copy)] 17 | pub struct MutualInhibit(u8); 18 | 19 | impl MutualInhibit { 20 | pub const fn new() -> Self { 21 | Self(0) 22 | } 23 | 24 | #[inline] 25 | pub fn update_dipsw(&mut self, dipsw: InhibitOverride) { 26 | self.0 = (self.0 & 0b1111_1100) | ((dipsw as u8) & 0b0000_0011); 27 | } 28 | 29 | #[inline] 30 | pub fn get_dipsw(&self) -> InhibitOverride { 31 | InhibitOverride::try_from(self.0 & 0b11).unwrap() // infallable 32 | } 33 | 34 | #[inline] 35 | #[allow(unused)] 36 | pub fn update_gpio(&mut self, gpio: InhibitOverride) { 37 | self.0 = self.0 & 0b1111_0011 | (((gpio as u8) & 0b0000_0011) << 2); 38 | } 39 | 40 | #[inline] 41 | pub fn set_gpio_player(&mut self, player: Player, state: bool) { 42 | match player { 43 | Player::Undefined => { 44 | self.0.set_bits(2..=3, 0b11 * state as u8); 45 | } 46 | x => { 47 | self.0.set_bit(x as usize + 1, state); 48 | } 49 | } 50 | } 51 | 52 | #[inline] 53 | #[allow(unused)] 54 | pub fn get_gpio(&self) -> InhibitOverride { 55 | InhibitOverride::try_from((self.0 >> 2) & 0b11).unwrap() // infallable 56 | } 57 | 58 | pub fn test_and_check(&mut self) -> Option { 59 | let cmp = ((self.0 >> 2) & 0b11) | (self.0 & 0b11); 60 | let prev = (self.0 >> 4) & 0b11; 61 | if cmp != prev { 62 | let after = InhibitOverride::try_from(cmp).unwrap(); // infallable 63 | self.0 = (self.0 & 0b1100_1111) | (cmp << 4); 64 | Some(after) 65 | } else { 66 | None 67 | } 68 | } 69 | 70 | pub async fn test_and_apply_output(&mut self, board: &Board) { 71 | // use card_terminal_adapter::types::RawPlayersInhibit; 72 | 73 | let inhibit_1p = &board.hardware.vend_sides[PLAYER_1_INDEX].out_inhibit; 74 | let inhibit_2p = &board.hardware.vend_sides[PLAYER_2_INDEX].out_inhibit; 75 | let serial_credit = &board.hardware.card_reader; 76 | 77 | let result = self.test_and_check(); 78 | 79 | if let Some(x) = result { 80 | let (p1, p2) = ((x as u8).get_bit(0), (x as u8).get_bit(1)); 81 | 82 | inhibit_1p.set_level(p1).await; 83 | inhibit_2p.set_level(p2).await; 84 | 85 | // ED785 doesn't support inhibit signal well.... 86 | // serial_credit 87 | // .send_inhibit(RawPlayersInhibit { p1, p2 }) 88 | // .await; 89 | 90 | serial_credit 91 | .send_transaction_availability(InhibitOverride::ForceInhibitGlobal != x) 92 | .await; 93 | } 94 | } 95 | } 96 | 97 | fn map_event_kind(evt: &InputEventKind) -> bool { 98 | match evt { 99 | InputEventKind::Pressed => true, 100 | InputEventKind::Released => false, 101 | _ => false, 102 | } 103 | } 104 | 105 | impl InputEvent { 106 | #[allow(unused)] 107 | pub fn test_mut_inh(&self, mut_inh: &mut MutualInhibit) -> Self { 108 | if let Some((player, status)) = { 109 | if matches!(self.event, InputEventKind::LongPressed(_)) { 110 | None 111 | } else if self.port == InputPortKind::Inhibit1P { 112 | Some((Player::Player1, map_event_kind(&self.event))) 113 | } else if self.port == InputPortKind::Inhibit2P { 114 | Some((Player::Player2, map_event_kind(&self.event))) 115 | } else { 116 | None 117 | } 118 | } { 119 | mut_inh.set_gpio_player(player, status); 120 | } 121 | 122 | *self 123 | } 124 | 125 | #[allow(unused)] 126 | pub fn test_mut_inh_then_ignore(&self, mut_inh: &mut MutualInhibit) -> Self { 127 | if let Some((player, status)) = { 128 | if matches!(self.event, InputEventKind::LongPressed(_)) { 129 | None 130 | } else if self.port == InputPortKind::Inhibit1P { 131 | Some((Player::Player1, map_event_kind(&self.event))) 132 | } else if self.port == InputPortKind::Inhibit2P { 133 | Some((Player::Player2, map_event_kind(&self.event))) 134 | } else { 135 | None 136 | } 137 | } { 138 | mut_inh.set_gpio_player(player, status); 139 | Self { 140 | port: InputPortKind::Nothing, 141 | event: self.event, 142 | } 143 | } else { 144 | *self 145 | } 146 | } 147 | 148 | pub async fn test_mut_inh_early_output( 149 | &self, 150 | mut_inh: &mut MutualInhibit, 151 | board: &Board, 152 | ) -> Self { 153 | if let Some((player, status)) = { 154 | if matches!(self.event, InputEventKind::LongPressed(_)) { 155 | None 156 | } else if self.port == InputPortKind::Inhibit1P { 157 | Some((Player::Player1, map_event_kind(&self.event))) 158 | } else if self.port == InputPortKind::Inhibit2P { 159 | Some((Player::Player2, map_event_kind(&self.event))) 160 | } else { 161 | None 162 | } 163 | } { 164 | mut_inh.set_gpio_player(player, status); 165 | 166 | mut_inh.test_and_apply_output(board).await; 167 | 168 | Self { 169 | port: InputPortKind::Nothing, 170 | event: self.event, 171 | } 172 | } else { 173 | *self 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/boards/billmock_0v3.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © 2023 Jinwoo Park (pmnxis@gmail.com) 3 | * 4 | * SPDX-License-Identifier: MIT OR Apache-2.0 5 | */ 6 | 7 | //! Hardware initialization code for BillMock Hardware Version 0.3 8 | //! The code follows on version 0.3 schematic 9 | //! https://github.com/pmnxis/BillMock-HW-RELEASE/blob/master/sch/BillMock-HW-0v3.pdf 10 | 11 | use embassy_stm32::exti::{Channel as HwChannel, ExtiInput}; 12 | use embassy_stm32::gpio::{Input, Level, Output, Pin, Pull, Speed}; 13 | use embassy_stm32::usart::{Config as UsartConfig, Uart}; 14 | use embassy_stm32::{bind_interrupts, peripherals}; 15 | use {defmt_rtt as _, panic_probe as _}; 16 | 17 | use super::{Hardware, SharedResource}; 18 | use super::{PLAYER_1_INDEX, PLAYER_2_INDEX}; 19 | use crate::components; 20 | use crate::components::dip_switch::DipSwitch; 21 | use crate::components::host_side_bill::HostSideBill; 22 | use crate::components::serial_device::CardReaderDevice; 23 | use crate::components::vend_side_bill::VendSideBill; 24 | use crate::semi_layer::buffered_opendrain::BufferedOpenDrain; 25 | use crate::types::buffered_opendrain_kind::BufferedOpenDrainKind; 26 | use crate::types::player::Player; 27 | 28 | bind_interrupts!(struct Irqs { 29 | USART2 => embassy_stm32::usart::InterruptHandler; 30 | }); 31 | 32 | static mut USART2_RX_BUF: [u8; components::serial_device::CARD_READER_RX_BUFFER_SIZE] = 33 | [0u8; components::serial_device::CARD_READER_RX_BUFFER_SIZE]; 34 | 35 | pub fn hardware_init_0v3( 36 | p: embassy_stm32::Peripherals, 37 | shared_resource: &'static SharedResource, 38 | ) -> Hardware { 39 | // USART2 initialization for CardReaderDevice 40 | let usart2_rx_buf = unsafe { &mut USART2_RX_BUF }; 41 | 42 | let usart2_config = { 43 | let mut ret = UsartConfig::default(); 44 | ret.baudrate = 115200; 45 | ret.assume_noise_free = false; 46 | ret 47 | }; 48 | 49 | let (usart2_tx, usart2_rx) = { 50 | let (tx, rx) = Uart::new( 51 | p.USART2, 52 | p.PA3, 53 | p.PA2, 54 | Irqs, 55 | p.DMA1_CH2, 56 | p.DMA1_CH1, 57 | usart2_config, 58 | ) 59 | .split(); 60 | (tx, rx.into_ring_buffered(usart2_rx_buf)) 61 | }; 62 | 63 | let async_input_event_ch = &shared_resource.async_input_event_ch.channel; 64 | 65 | Hardware { 66 | vend_sides: [ 67 | VendSideBill::new( 68 | Player::Player1, 69 | Output::new(p.PA1.degrade(), Level::Low, Speed::Low), // REAL0_INH 70 | ExtiInput::new( 71 | Input::new(p.PB2, Pull::None).degrade(), // REAL0_VND 72 | p.EXTI2.degrade(), // EXTI2 73 | ), 74 | ExtiInput::new( 75 | Input::new(p.PB14, Pull::None).degrade(), // REAL0_STR 76 | p.EXTI14.degrade(), // EXTI14 77 | ), 78 | async_input_event_ch, 79 | &shared_resource.arcade_players_timing[PLAYER_1_INDEX], 80 | ), 81 | VendSideBill::new( 82 | Player::Player2, 83 | Output::new(p.PA0.degrade(), Level::Low, Speed::Low), // REAL1_INH 84 | ExtiInput::new( 85 | Input::new(p.PB11, Pull::None).degrade(), // REAL1_VND 86 | p.EXTI11.degrade(), // EXTI11 (HW 0.2 was EXTI1 with PD1) 87 | ), 88 | ExtiInput::new( 89 | Input::new(p.PD1, Pull::None).degrade(), // REAL1_STR 90 | p.EXTI1.degrade(), // EXTI1 (HW 0.2 was EXTI11 with PB11) 91 | ), 92 | async_input_event_ch, 93 | &shared_resource.arcade_players_timing[PLAYER_2_INDEX], 94 | ), 95 | ], 96 | host_sides: [ 97 | HostSideBill::new( 98 | Player::Player1, 99 | ExtiInput::new( 100 | Input::new(p.PD0, Pull::None).degrade(), // VIRT0_INH 101 | p.EXTI0.degrade(), // EXTI0 102 | ), 103 | Output::new(p.PD3.degrade(), Level::Low, Speed::Low), // VIRT0_BSY 104 | Output::new(p.PD2.degrade(), Level::Low, Speed::Low), // VIRT0_VND 105 | Output::new(p.PB9.degrade(), Level::Low, Speed::Low), // VIRT0_JAM 106 | Output::new(p.PB3.degrade(), Level::Low, Speed::Low), // VIRT0_STR 107 | async_input_event_ch, 108 | &shared_resource.arcade_players_timing[PLAYER_1_INDEX], 109 | ), 110 | HostSideBill::new( 111 | Player::Player2, 112 | ExtiInput::new( 113 | Input::new(p.PA15, Pull::None).degrade(), // VIRT1_INH 114 | p.EXTI15.degrade(), // EXTI15 115 | ), 116 | Output::new(p.PB4.degrade(), Level::Low, Speed::Low), // VIRT1_BSY 117 | Output::new(p.PC13.degrade(), Level::Low, Speed::Low), // VIRT1_VND 118 | Output::new(p.PB8.degrade(), Level::Low, Speed::Low), // VIRT1_JAM 119 | Output::new(p.PB5.degrade(), Level::Low, Speed::Low), // VIRT1_STR 120 | async_input_event_ch, 121 | &shared_resource.arcade_players_timing[PLAYER_2_INDEX], 122 | ), 123 | ], 124 | indicators: [ 125 | BufferedOpenDrain::new( 126 | Output::new(p.PA4.degrade(), Level::High, Speed::Low), 127 | &shared_resource.indicator_timing, 128 | BufferedOpenDrainKind::Indicator(1).const_str(), 129 | ), 130 | BufferedOpenDrain::new( 131 | Output::new(p.PA5.degrade(), Level::High, Speed::Low), 132 | &shared_resource.indicator_timing, 133 | BufferedOpenDrainKind::Indicator(2).const_str(), 134 | ), 135 | ], 136 | dipsw: DipSwitch::new( 137 | Input::new(p.PC6.degrade(), Pull::Up), // DIPSW0 138 | Input::new(p.PA12.degrade(), Pull::Up), // DIPSW1 139 | Input::new(p.PA11.degrade(), Pull::Up), // DIPSW2 140 | Input::new(p.PA9.degrade(), Pull::Up), // DIPSW3 141 | Input::new(p.PB13.degrade(), Pull::Up), // DIPSW4 142 | Input::new(p.PB12.degrade(), Pull::Up), // DIPSW5 143 | ), 144 | card_reader: CardReaderDevice::new(usart2_tx, usart2_rx), 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/boards/billmock_0v2.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © 2023 Jinwoo Park (pmnxis@gmail.com) 3 | * 4 | * SPDX-License-Identifier: MIT OR Apache-2.0 5 | */ 6 | 7 | //! Hardware initialization code for BillMock Hardware Version 0.2 8 | //! The code follows on version 0.2 schematic 9 | //! https://github.com/pmnxis/BillMock-HW-RELEASE/blob/master/sch/BillMock-HW-0v2.pdf 10 | 11 | use embassy_stm32::exti::{Channel as HwChannel, ExtiInput}; 12 | use embassy_stm32::gpio::{Input, Level, Output, Pin, Pull, Speed}; 13 | use embassy_stm32::usart::{Config as UsartConfig, Uart}; 14 | use embassy_stm32::{bind_interrupts, peripherals}; 15 | use {defmt_rtt as _, panic_probe as _}; 16 | 17 | use super::{Hardware, SharedResource}; 18 | use super::{PLAYER_1_INDEX, PLAYER_2_INDEX}; 19 | use crate::components; 20 | use crate::components::dip_switch::DipSwitch; 21 | use crate::components::host_side_bill::HostSideBill; 22 | use crate::components::serial_device::CardReaderDevice; 23 | // Original verion 0.2 hardware require start_button module. But current spec deprecated it. 24 | // use crate::components::start_button::StartButton; 25 | use crate::components::vend_side_bill::VendSideBill; 26 | use crate::semi_layer::buffered_opendrain::BufferedOpenDrain; 27 | use crate::types::buffered_opendrain_kind::BufferedOpenDrainKind; 28 | use crate::types::player::Player; 29 | 30 | bind_interrupts!(struct Irqs { 31 | USART2 => embassy_stm32::usart::InterruptHandler; 32 | }); 33 | 34 | static mut USART2_RX_BUF: [u8; components::serial_device::CARD_READER_RX_BUFFER_SIZE] = 35 | [0u8; components::serial_device::CARD_READER_RX_BUFFER_SIZE]; 36 | 37 | pub fn hardware_init_0v2( 38 | p: embassy_stm32::Peripherals, 39 | shared_resource: &'static SharedResource, 40 | ) -> Hardware { 41 | // USART2 initialization for CardReaderDevice 42 | let usart2_rx_buf = unsafe { &mut USART2_RX_BUF }; 43 | 44 | let usart2_config = { 45 | let mut ret = UsartConfig::default(); 46 | ret.baudrate = 115200; 47 | ret.assume_noise_free = false; 48 | ret 49 | }; 50 | 51 | let (usart2_tx, usart2_rx) = { 52 | let (tx, rx) = Uart::new( 53 | p.USART2, 54 | p.PA3, 55 | p.PA2, 56 | Irqs, 57 | p.DMA1_CH2, 58 | p.DMA1_CH1, 59 | usart2_config, 60 | ) 61 | .split(); 62 | (tx, rx.into_ring_buffered(usart2_rx_buf)) 63 | }; 64 | 65 | let async_input_event_ch = &shared_resource.async_input_event_ch.channel; 66 | 67 | // 2023-08-17 , PA0 (Start1P Port out is not used anymore) 68 | 69 | Hardware { 70 | vend_sides: [ 71 | VendSideBill::new( 72 | Player::Player1, 73 | Output::new(p.PB0.degrade(), Level::Low, Speed::Low), // REAL_INH 74 | ExtiInput::new( 75 | Input::new(p.PB2, Pull::None).degrade(), // REAL_VND 76 | p.EXTI2.degrade(), // EXTI2 77 | ), 78 | ExtiInput::new( 79 | Input::new(p.PB14, Pull::None).degrade(), // REAL_JAM (moved from PB15 at HW v0.3) 80 | p.EXTI14.degrade(), // EXTI14 81 | ), 82 | async_input_event_ch, 83 | &shared_resource.arcade_players_timing[PLAYER_1_INDEX], 84 | ), 85 | VendSideBill::new( 86 | Player::Player2, 87 | Output::new(p.PA1.degrade(), Level::Low, Speed::Low), // LED_STR1 (Temporary) 88 | ExtiInput::new( 89 | Input::new(p.PD1, Pull::None).degrade(), // REAL_STR1 90 | p.EXTI1.degrade(), // EXTI1 91 | ), 92 | ExtiInput::new( 93 | Input::new(p.PB11, Pull::None).degrade(), // REAL_STR0 94 | p.EXTI11.degrade(), // EXTI11 95 | ), 96 | async_input_event_ch, 97 | &shared_resource.arcade_players_timing[PLAYER_2_INDEX], 98 | ), 99 | ], 100 | host_sides: [ 101 | HostSideBill::new( 102 | Player::Player1, 103 | ExtiInput::new( 104 | Input::new(p.PD0, Pull::None).degrade(), // VIRT0_INH 105 | p.EXTI0.degrade(), // EXTI0 106 | ), 107 | Output::new(p.PD3.degrade(), Level::Low, Speed::Low), // VIRT0_BSY 108 | Output::new(p.PD2.degrade(), Level::Low, Speed::Low), // VIRT0_VND 109 | Output::new(p.PB9.degrade(), Level::Low, Speed::Low), // VIRT0_JAM 110 | Output::new(p.PB3.degrade(), Level::Low, Speed::Low), // VIRT0_STR 111 | async_input_event_ch, 112 | &shared_resource.arcade_players_timing[PLAYER_1_INDEX], 113 | ), 114 | HostSideBill::new( 115 | Player::Player2, 116 | ExtiInput::new( 117 | Input::new(p.PA15, Pull::None).degrade(), // VIRT1_INH 118 | p.EXTI15.degrade(), // EXTI15 119 | ), 120 | Output::new(p.PB4.degrade(), Level::Low, Speed::Low), // VIRT1_BSY 121 | Output::new(p.PC13.degrade(), Level::Low, Speed::Low), // VIRT1_VND 122 | Output::new(p.PB8.degrade(), Level::Low, Speed::Low), // VIRT1_JAM 123 | Output::new(p.PB5.degrade(), Level::Low, Speed::Low), // VIRT1_STR 124 | async_input_event_ch, 125 | &shared_resource.arcade_players_timing[PLAYER_2_INDEX], 126 | ), 127 | ], 128 | indicators: [ 129 | BufferedOpenDrain::new( 130 | Output::new(p.PA4.degrade(), Level::High, Speed::Low), 131 | &shared_resource.indicator_timing, 132 | BufferedOpenDrainKind::Indicator(1).const_str(), 133 | ), 134 | BufferedOpenDrain::new( 135 | Output::new(p.PA5.degrade(), Level::High, Speed::Low), 136 | &shared_resource.indicator_timing, 137 | BufferedOpenDrainKind::Indicator(2).const_str(), 138 | ), 139 | ], 140 | dipsw: DipSwitch::new( 141 | Input::new(p.PC6.degrade(), Pull::Up), // DIPSW0 142 | Input::new(p.PA12.degrade(), Pull::Up), // DIPSW1 143 | Input::new(p.PA11.degrade(), Pull::Up), // DIPSW2 144 | Input::new(p.PA9.degrade(), Pull::Up), // DIPSW3 145 | Input::new(p.PB13.degrade(), Pull::Up), // DIPSW4 146 | Input::new(p.PB12.degrade(), Pull::Up), // DIPSW5 147 | ), 148 | card_reader: CardReaderDevice::new(usart2_tx, usart2_rx), 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /LICENSES/CC0-1.0.txt: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /src/boards/billmock_0v4.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © 2023 Jinwoo Park (pmnxis@gmail.com) 3 | * 4 | * SPDX-License-Identifier: MIT OR Apache-2.0 5 | */ 6 | 7 | //! Hardware initialization code for BillMock Hardware Version 0.4 8 | //! The code follows on version 0.4 schematic 9 | //! https://github.com/pmnxis/BillMock-HW-RELEASE/blob/master/sch/BillMock-HW-0v4.pdf 10 | 11 | use embassy_stm32::crc::{Config as CrcConfig, Crc, InputReverseConfig}; 12 | use embassy_stm32::exti::{Channel as HwChannel, ExtiInput}; 13 | use embassy_stm32::gpio::{Input, Level, Output, Pin, Pull, Speed}; 14 | use embassy_stm32::i2c::I2c; 15 | use embassy_stm32::time::Hertz; 16 | use embassy_stm32::usart::{Config as UsartConfig, Uart}; 17 | use embassy_stm32::{bind_interrupts, peripherals}; 18 | use embassy_time::Duration; 19 | use {defmt_rtt as _, panic_probe as _}; 20 | 21 | use super::{Hardware, SharedResource}; 22 | use super::{PLAYER_1_INDEX, PLAYER_2_INDEX}; 23 | use crate::components; 24 | use crate::components::dip_switch::DipSwitch; 25 | use crate::components::eeprom::Novella; 26 | use crate::components::host_side_bill::HostSideBill; 27 | use crate::components::serial_device::CardReaderDevice; 28 | use crate::components::vend_side_bill::VendSideBill; 29 | use crate::semi_layer::buffered_opendrain::BufferedOpenDrain; 30 | use crate::types::buffered_opendrain_kind::BufferedOpenDrainKind; 31 | use crate::types::player::Player; 32 | 33 | bind_interrupts!(struct Irqs { 34 | USART2 => embassy_stm32::usart::InterruptHandler; 35 | I2C1 => embassy_stm32::i2c::EventInterruptHandler, embassy_stm32::i2c::ErrorInterruptHandler; 36 | }); 37 | 38 | static mut USART2_RX_BUF: [u8; components::serial_device::CARD_READER_RX_BUFFER_SIZE] = 39 | [0u8; components::serial_device::CARD_READER_RX_BUFFER_SIZE]; 40 | 41 | const WAIT_DURATION_PER_PAGE: Duration = Duration::from_millis(20); // heuristic value 42 | 43 | pub fn hardware_init_0v4( 44 | p: embassy_stm32::Peripherals, 45 | shared_resource: &'static SharedResource, 46 | ) -> Hardware { 47 | // USART2 initialization for CardReaderDevice 48 | let usart2_rx_buf = unsafe { &mut USART2_RX_BUF }; 49 | 50 | let usart2_config = { 51 | let mut ret = UsartConfig::default(); 52 | ret.baudrate = 115200; 53 | ret.assume_noise_free = false; 54 | ret 55 | }; 56 | 57 | let (usart2_tx, usart2_rx) = { 58 | let (tx, rx) = Uart::new( 59 | p.USART2, 60 | p.PA3, 61 | p.PA2, 62 | Irqs, 63 | p.DMA1_CH2, 64 | p.DMA1_CH1, 65 | usart2_config, 66 | ) 67 | .unwrap() 68 | .split(); 69 | (tx, rx.into_ring_buffered(usart2_rx_buf)) 70 | }; 71 | 72 | let i2c_config = { 73 | let mut ret: embassy_stm32::i2c::Config = embassy_stm32::i2c::Config::default(); 74 | ret.timeout = WAIT_DURATION_PER_PAGE; 75 | ret 76 | }; 77 | 78 | let i2c = I2c::new( 79 | p.I2C1, 80 | p.PB8, 81 | p.PB9, 82 | Irqs, 83 | p.DMA1_CH4, 84 | p.DMA1_CH3, 85 | Hertz(400_000), 86 | i2c_config, 87 | ); 88 | 89 | // InputReverseConfig::Halfword 90 | let Ok(crc_config) = CrcConfig::new(InputReverseConfig::Word, false, 0xA097) else { 91 | panic!("Something went horribly wrong") 92 | }; 93 | 94 | let crc = Crc::new(p.CRC, crc_config); 95 | 96 | let async_input_event_ch = &shared_resource.async_input_event_ch.channel; 97 | 98 | Hardware { 99 | vend_sides: [ 100 | VendSideBill::new( 101 | Player::Player1, 102 | Output::new(p.PA1.degrade(), Level::Low, Speed::Low), // REAL0_INH 103 | ExtiInput::new( 104 | Input::new(p.PB2, Pull::None).degrade(), // REAL0_VND 105 | p.EXTI2.degrade(), // EXTI2 106 | ), 107 | ExtiInput::new( 108 | Input::new(p.PB14, Pull::None).degrade(), // REAL0_STR 109 | p.EXTI14.degrade(), // EXTI14 110 | ), 111 | async_input_event_ch, 112 | &shared_resource.arcade_players_timing[PLAYER_1_INDEX], 113 | ), 114 | VendSideBill::new( 115 | Player::Player2, 116 | Output::new(p.PA0.degrade(), Level::Low, Speed::Low), // REAL1_INH 117 | ExtiInput::new( 118 | Input::new(p.PB11, Pull::None).degrade(), // REAL1_VND 119 | p.EXTI11.degrade(), // EXTI11 120 | ), 121 | ExtiInput::new( 122 | Input::new(p.PD1, Pull::None).degrade(), // REAL1_STR 123 | p.EXTI1.degrade(), // EXTI1 124 | ), 125 | async_input_event_ch, 126 | &shared_resource.arcade_players_timing[PLAYER_2_INDEX], 127 | ), 128 | ], 129 | host_sides: [ 130 | HostSideBill::new( 131 | Player::Player1, 132 | ExtiInput::new( 133 | Input::new(p.PD0, Pull::None).degrade(), // VIRT0_INH 134 | p.EXTI0.degrade(), // EXTI0 135 | ), 136 | Output::new(p.PD3.degrade(), Level::Low, Speed::Low), // VIRT0_BSY 137 | Output::new(p.PD2.degrade(), Level::Low, Speed::Low), // VIRT0_VND 138 | Output::new(p.PB4.degrade(), Level::Low, Speed::Low), // VIRT0_JAM 139 | Output::new(p.PB3.degrade(), Level::Low, Speed::Low), // VIRT0_STR 140 | async_input_event_ch, 141 | &shared_resource.arcade_players_timing[PLAYER_1_INDEX], 142 | ), 143 | HostSideBill::new( 144 | Player::Player2, 145 | ExtiInput::new( 146 | Input::new(p.PA15, Pull::None).degrade(), // VIRT1_INH 147 | p.EXTI15.degrade(), // EXTI15 148 | ), 149 | Output::new(p.PC13.degrade(), Level::Low, Speed::Low), // VIRT1_BSY 150 | Output::new(p.PB5.degrade(), Level::Low, Speed::Low), // VIRT1_VND 151 | Output::new(p.PC15.degrade(), Level::Low, Speed::Low), // VIRT1_JAM 152 | Output::new(p.PC14.degrade(), Level::Low, Speed::Low), // VIRT1_STR 153 | async_input_event_ch, 154 | &shared_resource.arcade_players_timing[PLAYER_2_INDEX], 155 | ), 156 | ], 157 | indicators: [ 158 | BufferedOpenDrain::new( 159 | Output::new(p.PA4.degrade(), Level::High, Speed::Low), 160 | &shared_resource.indicator_timing, 161 | BufferedOpenDrainKind::Indicator(1).const_str(), 162 | ), 163 | BufferedOpenDrain::new( 164 | Output::new(p.PA5.degrade(), Level::High, Speed::Low), 165 | &shared_resource.indicator_timing, 166 | BufferedOpenDrainKind::Indicator(2).const_str(), 167 | ), 168 | ], 169 | dipsw: DipSwitch::new( 170 | Input::new(p.PC6.degrade(), Pull::Up), // DIPSW0 171 | Input::new(p.PA12.degrade(), Pull::Up), // DIPSW1 172 | Input::new(p.PA11.degrade(), Pull::Up), // DIPSW2 173 | Input::new(p.PA9.degrade(), Pull::Up), // DIPSW3 174 | Input::new(p.PB13.degrade(), Pull::Up), // DIPSW4 175 | Input::new(p.PB12.degrade(), Pull::Up), // DIPSW5 176 | ), 177 | card_reader: CardReaderDevice::new(usart2_tx, usart2_rx), 178 | eeprom: Novella::const_new( 179 | i2c, 180 | crc, 181 | embassy_stm32::gpio::OutputOpenDrain::new(p.PF0, Level::Low, Speed::Low, Pull::None), 182 | ), 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/boards/billmock_mini_0v4.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © 2023 Jinwoo Park (pmnxis@gmail.com) 3 | * 4 | * SPDX-License-Identifier: MIT OR Apache-2.0 5 | */ 6 | 7 | //! Hardware initialization code for BillMock Hardware Version mini 0.4 8 | //! The code follows on version mini 0.4 schematic 9 | //! https://github.com/pmnxis/BillMock-HW-RELEASE/blob/master/sch/BillMock-Mini-HW-0v4.pdf 10 | 11 | use embassy_stm32::crc::{Config as CrcConfig, Crc, InputReverseConfig}; 12 | use embassy_stm32::exti::{Channel as HwChannel, ExtiInput}; 13 | use embassy_stm32::gpio::{Input, Level, Output, Pin, Pull, Speed}; 14 | use embassy_stm32::i2c::I2c; 15 | use embassy_stm32::time::Hertz; 16 | use embassy_stm32::usart::{Config as UsartConfig, Uart}; 17 | use embassy_stm32::{bind_interrupts, peripherals}; 18 | use embassy_time::Duration; 19 | use {defmt_rtt as _, panic_probe as _}; 20 | 21 | use super::{Hardware, SharedResource}; 22 | use super::{PLAYER_1_INDEX, PLAYER_2_INDEX}; 23 | use crate::components; 24 | use crate::components::dip_switch::DipSwitch; 25 | use crate::components::eeprom::Novella; 26 | use crate::components::host_side_bill::HostSideBill; 27 | use crate::components::serial_device::CardReaderDevice; 28 | use crate::components::vend_side_bill::VendSideBill; 29 | use crate::semi_layer::buffered_opendrain::BufferedOpenDrain; 30 | use crate::types::buffered_opendrain_kind::BufferedOpenDrainKind; 31 | use crate::types::player::Player; 32 | 33 | bind_interrupts!(struct Irqs { 34 | USART2 => embassy_stm32::usart::InterruptHandler; 35 | I2C1 => embassy_stm32::i2c::EventInterruptHandler, embassy_stm32::i2c::ErrorInterruptHandler; 36 | }); 37 | 38 | static mut USART2_RX_BUF: [u8; components::serial_device::CARD_READER_RX_BUFFER_SIZE] = 39 | [0u8; components::serial_device::CARD_READER_RX_BUFFER_SIZE]; 40 | 41 | const WAIT_DURATION_PER_PAGE: Duration = Duration::from_millis(20); // heuristic value 42 | 43 | pub fn hardware_init_mini_0v4( 44 | p: embassy_stm32::Peripherals, 45 | shared_resource: &'static SharedResource, 46 | ) -> Hardware { 47 | // USART2 initialization for CardReaderDevice 48 | let usart2_rx_buf = unsafe { &mut USART2_RX_BUF }; 49 | 50 | let usart2_config = { 51 | let mut ret: UsartConfig = UsartConfig::default(); 52 | ret.baudrate = 115200; 53 | ret.assume_noise_free = false; 54 | ret.detect_previous_overrun = true; 55 | ret 56 | }; 57 | 58 | let (usart2_tx, usart2_rx) = { 59 | let (tx, rx) = Uart::new( 60 | p.USART2, 61 | p.PA3, 62 | p.PA2, 63 | Irqs, 64 | p.DMA1_CH2, 65 | p.DMA1_CH1, 66 | usart2_config, 67 | ) 68 | .unwrap() 69 | .split(); 70 | (tx, rx.into_ring_buffered(usart2_rx_buf)) 71 | }; 72 | 73 | let i2c_config = { 74 | let mut ret: embassy_stm32::i2c::Config = embassy_stm32::i2c::Config::default(); 75 | ret.timeout = WAIT_DURATION_PER_PAGE; 76 | ret 77 | }; 78 | 79 | let i2c = I2c::new( 80 | p.I2C1, 81 | p.PB8, 82 | p.PB9, 83 | Irqs, 84 | p.DMA1_CH4, 85 | p.DMA1_CH3, 86 | Hertz(400_000), 87 | Default::default(), 88 | i2c_config, 89 | ); 90 | 91 | // InputReverseConfig::Halfword 92 | let Ok(crc_config) = CrcConfig::new(InputReverseConfig::Word, false, 0xA097) else { 93 | panic!("Something went horribly wrong") 94 | }; 95 | 96 | let crc = Crc::new(p.CRC, crc_config); 97 | 98 | let async_input_event_ch = &shared_resource.async_input_event_ch.channel; 99 | 100 | Hardware { 101 | vend_sides: [ 102 | VendSideBill::new( 103 | Player::Player1, 104 | Output::new(p.PA1.degrade(), Level::Low, Speed::Low), // REAL0_INH 105 | ExtiInput::new( 106 | Input::new(p.PB14, Pull::None).degrade(), // REAL0_VND 107 | p.EXTI14.degrade(), // EXTI14 108 | ), 109 | ExtiInput::new( 110 | Input::new(p.PB2, Pull::None).degrade(), // REAL0_STR 111 | p.EXTI2.degrade(), // EXTI2 112 | ), 113 | async_input_event_ch, 114 | &shared_resource.arcade_players_timing[PLAYER_1_INDEX], 115 | ), 116 | VendSideBill::new( 117 | Player::Player2, 118 | Output::new(p.PA0.degrade(), Level::Low, Speed::Low), // REAL1_INH 119 | ExtiInput::new( 120 | Input::new(p.PD1, Pull::None).degrade(), // REAL1_VND 121 | p.EXTI1.degrade(), // EXTI1 122 | ), 123 | ExtiInput::new( 124 | Input::new(p.PB11, Pull::None).degrade(), // REAL1_STR 125 | p.EXTI11.degrade(), // EXTI11 126 | ), 127 | async_input_event_ch, 128 | &shared_resource.arcade_players_timing[PLAYER_2_INDEX], 129 | ), 130 | ], 131 | host_sides: [ 132 | HostSideBill::new( 133 | Player::Player1, 134 | ExtiInput::new( 135 | Input::new(p.PD0, Pull::None).degrade(), // VIRT0_INH 136 | p.EXTI0.degrade(), // EXTI0 137 | ), 138 | Output::new(p.PD3.degrade(), Level::Low, Speed::Low), // VIRT0_BSY 139 | Output::new(p.PD2.degrade(), Level::Low, Speed::Low), // VIRT0_VND 140 | Output::new(p.PB4.degrade(), Level::Low, Speed::Low), // VIRT0_JAM 141 | Output::new(p.PB3.degrade(), Level::Low, Speed::Low), // VIRT0_STR 142 | async_input_event_ch, 143 | &shared_resource.arcade_players_timing[PLAYER_1_INDEX], 144 | ), 145 | HostSideBill::new( 146 | Player::Player2, 147 | ExtiInput::new( 148 | Input::new(p.PA15, Pull::None).degrade(), // VIRT1_INH 149 | p.EXTI15.degrade(), // EXTI15 150 | ), 151 | Output::new(p.PC14.degrade(), Level::Low, Speed::Low), // VIRT1_BSY 152 | Output::new(p.PC13.degrade(), Level::Low, Speed::Low), // VIRT1_VND 153 | Output::new(p.PB5.degrade(), Level::Low, Speed::Low), // VIRT1_JAM 154 | Output::new(p.PC15.degrade(), Level::Low, Speed::Low), // VIRT1_STR 155 | async_input_event_ch, 156 | &shared_resource.arcade_players_timing[PLAYER_2_INDEX], 157 | ), 158 | ], 159 | indicators: [ 160 | BufferedOpenDrain::new( 161 | Output::new(p.PA5.degrade(), Level::High, Speed::Low), 162 | &shared_resource.indicator_timing, 163 | BufferedOpenDrainKind::Indicator(1).const_str(), 164 | ), 165 | BufferedOpenDrain::new( 166 | Output::new(p.PA4.degrade(), Level::High, Speed::Low), 167 | &shared_resource.indicator_timing, 168 | BufferedOpenDrainKind::Indicator(2).const_str(), 169 | ), 170 | ], 171 | dipsw: DipSwitch::new( 172 | Input::new(p.PC6.degrade(), Pull::Up), // DIPSW0 173 | Input::new(p.PA12.degrade(), Pull::Up), // DIPSW1 174 | Input::new(p.PA11.degrade(), Pull::Up), // DIPSW2 175 | Input::new(p.PA9.degrade(), Pull::Up), // DIPSW3 176 | Input::new(p.PB13.degrade(), Pull::Up), // DIPSW4 177 | Input::new(p.PB12.degrade(), Pull::Up), // DIPSW5 178 | ), 179 | card_reader: CardReaderDevice::new(usart2_tx, usart2_rx), 180 | eeprom: Novella::const_new( 181 | i2c, 182 | crc, 183 | embassy_stm32::gpio::OutputOpenDrain::new(p.PF0, Level::Low, Speed::Low, Pull::None), 184 | ), 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/boards/billmock_mini_0v5.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © 2023 Jinwoo Park (pmnxis@gmail.com) 3 | * 4 | * SPDX-License-Identifier: MIT OR Apache-2.0 5 | */ 6 | 7 | //! Hardware initialization code for BillMock Hardware Version mini 0.4 8 | //! The code follows on version mini 0.4 schematic 9 | //! https://github.com/pmnxis/BillMock-HW-RELEASE/blob/master/sch/BillMock-Mini-HW-0v5.pdf 10 | 11 | use embassy_stm32::crc::{Config as CrcConfig, Crc, InputReverseConfig}; 12 | use embassy_stm32::exti::{Channel as HwChannel, ExtiInput}; 13 | use embassy_stm32::gpio::{Input, Level, Output, Pin, Pull, Speed}; 14 | use embassy_stm32::i2c::I2c; 15 | use embassy_stm32::time::Hertz; 16 | use embassy_stm32::usart::{Config as UsartConfig, Uart}; 17 | use embassy_stm32::{bind_interrupts, peripherals}; 18 | use embassy_time::Duration; 19 | use {defmt_rtt as _, panic_probe as _}; 20 | 21 | use super::{Hardware, SharedResource}; 22 | use super::{PLAYER_1_INDEX, PLAYER_2_INDEX}; 23 | use crate::components; 24 | use crate::components::dip_switch::DipSwitch; 25 | use crate::components::eeprom::Novella; 26 | use crate::components::host_side_bill::HostSideBill; 27 | use crate::components::serial_device::CardReaderDevice; 28 | use crate::components::vend_side_bill::VendSideBill; 29 | use crate::semi_layer::buffered_opendrain::BufferedOpenDrain; 30 | #[cfg(feature = "svc_button")] 31 | use crate::semi_layer::buffered_wait::BufferedWait; 32 | use crate::types::buffered_opendrain_kind::BufferedOpenDrainKind; 33 | use crate::types::player::Player; 34 | 35 | bind_interrupts!(struct Irqs { 36 | USART2 => embassy_stm32::usart::InterruptHandler; 37 | I2C1 => embassy_stm32::i2c::EventInterruptHandler, embassy_stm32::i2c::ErrorInterruptHandler; 38 | }); 39 | 40 | static mut USART2_RX_BUF: [u8; components::serial_device::CARD_READER_RX_BUFFER_SIZE] = 41 | [0u8; components::serial_device::CARD_READER_RX_BUFFER_SIZE]; 42 | 43 | const WAIT_DURATION_PER_PAGE: Duration = Duration::from_millis(20); // heuristic value 44 | 45 | pub fn hardware_init_mini_0v5( 46 | p: embassy_stm32::Peripherals, 47 | shared_resource: &'static SharedResource, 48 | ) -> Hardware { 49 | // USART2 initialization for CardReaderDevice 50 | let usart2_rx_buf = unsafe { &mut *core::ptr::addr_of_mut!(USART2_RX_BUF) }; 51 | 52 | let usart2_config = { 53 | let mut ret: UsartConfig = UsartConfig::default(); 54 | ret.baudrate = 115200; 55 | ret.assume_noise_free = false; 56 | ret.detect_previous_overrun = true; 57 | ret 58 | }; 59 | 60 | let (usart2_tx, usart2_rx) = { 61 | let (tx, rx) = Uart::new( 62 | p.USART2, 63 | p.PA3, 64 | p.PA2, 65 | Irqs, 66 | p.DMA1_CH2, 67 | p.DMA1_CH1, 68 | usart2_config, 69 | ) 70 | .unwrap() 71 | .split(); 72 | (tx, rx.into_ring_buffered(usart2_rx_buf)) 73 | }; 74 | 75 | let i2c_config = { 76 | let mut ret: embassy_stm32::i2c::Config = embassy_stm32::i2c::Config::default(); 77 | ret.timeout = WAIT_DURATION_PER_PAGE; 78 | ret 79 | }; 80 | 81 | let i2c = I2c::new( 82 | p.I2C1, 83 | p.PB8, 84 | p.PB9, 85 | Irqs, 86 | p.DMA1_CH4, 87 | p.DMA1_CH3, 88 | Hertz(400_000), 89 | i2c_config, 90 | ); 91 | 92 | // InputReverseConfig::Halfword 93 | let Ok(crc_config) = CrcConfig::new(InputReverseConfig::Word, false, 0xA097) else { 94 | panic!("Something went horribly wrong") 95 | }; 96 | 97 | let crc = Crc::new(p.CRC, crc_config); 98 | 99 | let async_input_event_ch = &shared_resource.async_input_event_ch.channel; 100 | 101 | #[cfg(feature = "svc_button")] 102 | let (svc_p, svc_str) = 103 | crate::types::input_port::InputPortKind::SvcButton.to_raw_and_const_str(Player::Undefined); 104 | 105 | Hardware { 106 | vend_sides: [ 107 | VendSideBill::new( 108 | Player::Player1, 109 | Output::new(p.PA1.degrade(), Level::Low, Speed::Low), // REAL0_INH 110 | ExtiInput::new( 111 | Input::new(p.PB14, Pull::None).degrade(), // REAL0_VND 112 | p.EXTI14.degrade(), // EXTI14 113 | ), 114 | ExtiInput::new( 115 | Input::new(p.PB2, Pull::None).degrade(), // REAL0_STR 116 | p.EXTI2.degrade(), // EXTI2 117 | ), 118 | async_input_event_ch, 119 | &shared_resource.arcade_players_timing[PLAYER_1_INDEX], 120 | ), 121 | VendSideBill::new( 122 | Player::Player2, 123 | Output::new(p.PA0.degrade(), Level::Low, Speed::Low), // REAL1_INH 124 | ExtiInput::new( 125 | Input::new(p.PD1, Pull::None).degrade(), // REAL1_VND 126 | p.EXTI1.degrade(), // EXTI1 127 | ), 128 | ExtiInput::new( 129 | Input::new(p.PB11, Pull::None).degrade(), // REAL1_STR 130 | p.EXTI11.degrade(), // EXTI11 131 | ), 132 | async_input_event_ch, 133 | &shared_resource.arcade_players_timing[PLAYER_2_INDEX], 134 | ), 135 | ], 136 | host_sides: [ 137 | HostSideBill::new( 138 | Player::Player1, 139 | ExtiInput::new( 140 | Input::new(p.PD0, Pull::None).degrade(), // VIRT0_INH 141 | p.EXTI0.degrade(), // EXTI0 142 | ), 143 | Output::new(p.PD3.degrade(), Level::Low, Speed::Low), // VIRT0_BSY 144 | Output::new(p.PD2.degrade(), Level::Low, Speed::Low), // VIRT0_VND 145 | Output::new(p.PB4.degrade(), Level::Low, Speed::Low), // VIRT0_JAM 146 | Output::new(p.PB3.degrade(), Level::Low, Speed::Low), // VIRT0_STR 147 | async_input_event_ch, 148 | &shared_resource.arcade_players_timing[PLAYER_1_INDEX], 149 | ), 150 | HostSideBill::new( 151 | Player::Player2, 152 | ExtiInput::new( 153 | Input::new(p.PA15, Pull::None).degrade(), // VIRT1_INH 154 | p.EXTI15.degrade(), // EXTI15 155 | ), 156 | Output::new(p.PC14.degrade(), Level::Low, Speed::Low), // VIRT1_BSY 157 | Output::new(p.PC13.degrade(), Level::Low, Speed::Low), // VIRT1_VND 158 | Output::new(p.PB5.degrade(), Level::Low, Speed::Low), // VIRT1_JAM 159 | Output::new(p.PC15.degrade(), Level::Low, Speed::Low), // VIRT1_STR 160 | async_input_event_ch, 161 | &shared_resource.arcade_players_timing[PLAYER_2_INDEX], 162 | ), 163 | ], 164 | indicators: [ 165 | BufferedOpenDrain::new( 166 | Output::new(p.PA5.degrade(), Level::High, Speed::Low), 167 | &shared_resource.indicator_timing, 168 | BufferedOpenDrainKind::Indicator(1).const_str(), 169 | ), 170 | BufferedOpenDrain::new( 171 | Output::new(p.PA4.degrade(), Level::High, Speed::Low), 172 | &shared_resource.indicator_timing, 173 | BufferedOpenDrainKind::Indicator(2).const_str(), 174 | ), 175 | ], 176 | dipsw: DipSwitch::new( 177 | Input::new(p.PC6.degrade(), Pull::Up), // DIPSW0 178 | Input::new(p.PA12.degrade(), Pull::Up), // DIPSW1 179 | Input::new(p.PA11.degrade(), Pull::Up), // DIPSW2 180 | Input::new(p.PA9.degrade(), Pull::Up), // DIPSW3 181 | Input::new(p.PB13.degrade(), Pull::Up), // DIPSW4 182 | Input::new(p.PB12.degrade(), Pull::Up), // DIPSW5 183 | ), 184 | card_reader: CardReaderDevice::new(usart2_tx, usart2_rx), 185 | eeprom: Novella::const_new( 186 | i2c, 187 | crc, 188 | embassy_stm32::gpio::OutputOpenDrain::new(p.PF0, Level::Low, Speed::Low, Pull::None), 189 | ), 190 | #[cfg(feature = "svc_button")] 191 | svc_button: BufferedWait::new( 192 | ExtiInput::new( 193 | Input::new(p.PA8, Pull::Up).degrade(), // SW_SERVICE, need pull-up 194 | p.EXTI8.degrade(), // EXTI8 195 | ), 196 | async_input_event_ch, 197 | svc_p, 198 | svc_str, 199 | ), 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /card-terminal-adapter/src/lib.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © 2023 Jinwoo Park (pmnxis@gmail.com) 3 | * 4 | * SPDX-License-Identifier: MIT OR Apache-2.0 5 | */ 6 | 7 | //! This adapter is designed to separate the NDA code section from the actual packet parser/generator. 8 | //! It also offers the possibility of connecting with different card terminal implementations in the future. 9 | //! However, it's important to note that the initial adapter is fitted for the KICC ED785. 10 | //! Thus other card terminal manufacturers may need to customize 11 | //! specific firmware while considering the existing adapter as a reference. 12 | 13 | #![no_std] 14 | #![feature(const_trait_impl)] 15 | 16 | pub mod types; 17 | 18 | use types::*; 19 | 20 | #[derive(Debug, defmt::Format, Clone, Eq, PartialEq, Ord, PartialOrd)] 21 | pub enum TerminalVersion { 22 | ArcadeSpecificLatest, 23 | ArcadeSpecificLegacy, 24 | GenericPriceIncomeType, 25 | Experimental, 26 | Unknown, 27 | } 28 | 29 | #[derive(Debug, defmt::Format, Clone, Eq, PartialEq, Ord, PartialOrd)] 30 | pub enum CardTerminalError { 31 | /// Given checksum has fault checksum with known packet frame spec 32 | BadChecksum, 33 | /// Bad length, 34 | BadLength, 35 | /// Unsupported parameter is included on data section (text style) 36 | UnsupportedParameter, 37 | /// Basic packet frame is correct, but cannot determine internal spec 38 | UnsupportedSpec, 39 | /// Invalid Frame, it mean totally crashed data 40 | InvalidFrame, 41 | /// There is no request for the spec or not implemented 42 | VarientNotSupportRequest, 43 | /// Wrong src, suggest to check RX/TX are shorted. 44 | WrongSource, 45 | /// Failed Response 46 | FailedResponse, 47 | } 48 | 49 | #[derive(Debug, defmt::Format, Clone, Eq, PartialEq, Ord, PartialOrd)] 50 | pub enum TidStatus { 51 | Unknown, 52 | Changed, 53 | Unchanged, 54 | } 55 | 56 | #[derive(PartialEq, Eq, Clone, defmt::Format)] 57 | pub enum CardTerminalRxCmd { 58 | /// Ack signal 59 | Ack, 60 | /// Nack signal 61 | Nack, 62 | /// Request device information that related this board and firmware 63 | RequestDeviceInfo, 64 | /// For generic credit card terminal (not arcade specific customized version) 65 | AlertPaymentIncomePrice(RawU24Price), 66 | /// For arcade speicific customied version credit card terminal 67 | AlertPaymentIncomeArcade(RawU24IncomeArcade), 68 | /// Detail pakcet data should be parsed with additional function call. 69 | /// using additional function call for avoid queue size being huge. 70 | ResponseSaleSlotInfo, 71 | /// 0xFB 0x14 0x02 72 | /// Detail pakcet data should be parsed with additional function call. 73 | /// using additional function call for avoid queue size being huge. 74 | ResponseTerminalInfo(TidStatus, TerminalVersion), 75 | /// Set Pulse State 76 | RequestKeepPulseState(PulseStateRequest), 77 | } 78 | 79 | #[derive(PartialEq, Eq, Clone, defmt::Format)] 80 | pub enum CardTerminalTxCmd { 81 | /// Ack signal 82 | Ack, 83 | /// Nack signal 84 | Nack, 85 | /// Generate info from static/const data (firmware version and device name) 86 | ResponseDeviceInfo, 87 | /// For arcade speicific customied version credit card terminal 88 | PushCoinPaperAcceptorIncome(RawU24IncomeArcade), 89 | /// Get sale slot from card terminal 90 | RequestSaleSlotInfo, 91 | /// Overwrite sale slot info to card terminal 92 | /// It's for rollback or treat as inihibit action 93 | PushSaleSlotInfo, 94 | /// [Deprecated] 95 | /// Mixed request of PushSaleSlotInfo, 96 | /// Unfortunately, this feature is still unstable due to sequence logic issues 97 | /// in the real environment. Therefore, we do not recommend using it at this time. 98 | PushSaleSlotInfoPartialInhibit(RawPlayersInhibit), 99 | /// Set availability of transactions 100 | /// New method instead of PushSaleSlotInfoPartialInhibit 101 | SetTransactionAvailability(bool), 102 | /// Request terminal info, include TID, terminal program version etc. 103 | RequestTerminalInfo, 104 | /// Display ROM (P1/P2 Card and Coin Meter) 105 | DisplayRom, 106 | /// Display HW Info (S/N, FW version ... etc) 107 | DisplayHwInfo, 108 | /// Display Warnings 109 | DisplayWarning(CardTerminalDisplayWarning), 110 | } 111 | 112 | #[derive(PartialEq, Eq, Clone, Copy, defmt::Format)] 113 | pub enum CardTerminalDisplayWarning { 114 | RequireArcadeSpecificVersion, 115 | RequireLatestTerminalVersion, 116 | WarnExperimentalVesion, 117 | WarnUnknown, 118 | WarnEepromFactoryReset, 119 | } 120 | 121 | pub const TID_LEN: usize = 10; 122 | pub const FW_VER_LEN: usize = 5; 123 | pub const DEV_SN_LEN: usize = 12; 124 | pub const GIT_HASH_LEN: usize = 9; 125 | 126 | // #[const_trait] 127 | pub trait CardTerminalConst { 128 | fn is_nda() -> bool; 129 | } 130 | 131 | pub trait CardTerminalRxParse { 132 | fn pre_parse_common(&self, raw: &[u8]) -> Result; 133 | 134 | // Parse ResponseSaleSlotInfo with after pre_parse_common call 135 | fn post_parse_response_sale_slot_info( 136 | &self, 137 | raw: &[u8], 138 | ) -> Result; 139 | 140 | // Parse ResponseTerminalInfo with after pre_parse_common call 141 | fn post_parse_response_terminal_info( 142 | &self, 143 | raw: &[u8], 144 | prev_terminal_id: &RawTerminalId, 145 | ) -> Result<(CardTerminalRxCmd, RawTerminalId), CardTerminalError>; 146 | } 147 | 148 | pub trait CardTerminalTxGen { 149 | // Generate ACK signal to send 150 | fn response_ack<'a>(&self, buffer: &'a mut [u8]) -> &'a [u8]; 151 | 152 | // Generate NACK signal to send 153 | fn response_nack<'a>(&self, buffer: &'a mut [u8]) -> &'a [u8]; 154 | 155 | /// Generate ResponseDeviceInfo signal to send 156 | /// Response for requesting device information (RequestDeviceInfo) 157 | fn response_device_info<'a, 'b>( 158 | &self, 159 | buffer: &'a mut [u8], 160 | model_version: &'b [u8; FW_VER_LEN], 161 | serial_number: &'b [u8; DEV_SN_LEN], 162 | ) -> &'a [u8]; 163 | 164 | /// Generate PushCoinPaperAcceptorIncome signal to send 165 | fn alert_coin_paper_acceptor_income<'a>( 166 | &self, 167 | buffer: &'a mut [u8], 168 | income: RawU24IncomeArcade, 169 | ) -> &'a [u8]; 170 | 171 | /// Generate PushSaleSlotInfo signal to send 172 | /// This action send for all slots without modification 173 | fn push_sale_slot_info<'a>( 174 | &self, 175 | buffer: &'a mut [u8], 176 | port_backup: &CardReaderPortBackup, 177 | ) -> &'a [u8]; 178 | 179 | /// [Deprecated] 180 | /// Generate PushSaleSlotInfoPartialInhibit signal to send 181 | /// This action send with modificated slots to inhibit sale slot for inhibit behavior 182 | fn push_sale_slot_info_partial_inhibit<'a>( 183 | &self, 184 | buffer: &'a mut [u8], 185 | port_backup: &CardReaderPortBackup, 186 | ) -> &'a [u8]; 187 | 188 | /// Generate SetTransactionAvailability signal to send 189 | /// This is inversed boolean of SetInhibit 190 | fn push_transaction_availability<'a>(&self, buffer: &'a mut [u8], is_avail: bool) -> &'a [u8]; 191 | 192 | /// Generate RequestSaleSlotInfo signal to send 193 | fn request_sale_slot_info<'a>(&self, buffer: &'a mut [u8]) -> &'a [u8]; 194 | 195 | /// Generate RequestTerminalInfo signal to send 196 | fn request_terminal_info<'a>(&self, buffer: &'a mut [u8]) -> &'a [u8]; 197 | 198 | /// Generate DisplayRom signal to send 199 | /// Display card / coin count for player 1 and 2 on LCD of card terminal. 200 | fn display_rom<'a>( 201 | &self, 202 | buffer: &'a mut [u8], 203 | git_hash: &'a [u8; GIT_HASH_LEN], 204 | terminal_id: &[u8; TID_LEN], 205 | p1_card: u32, 206 | p2_card: u32, 207 | p1_coin: u32, 208 | p2_coin: u32, 209 | ) -> &'a [u8]; 210 | 211 | /// Generate DisplayHwInfo signal to send 212 | /// Display hardware information, boot count, uptime and etc. 213 | fn display_hw_info<'a, 'b>( 214 | &self, 215 | buffer: &'a mut [u8], 216 | model_version: &'b [u8; FW_VER_LEN], 217 | serial_number: &'b [u8; DEV_SN_LEN], 218 | terminal_id: &[u8; TID_LEN], 219 | hw_boot_cnt: u32, 220 | uptime_minutes: u32, 221 | ) -> &'a [u8]; 222 | 223 | /// Generate DisplayWarning signal to send 224 | /// Display warning that need to update to latest terminal version firmware or something 225 | fn display_warning<'a>( 226 | &self, 227 | buffer: &'a mut [u8], 228 | warn_kind: CardTerminalDisplayWarning, 229 | ) -> &'a [u8]; 230 | } 231 | -------------------------------------------------------------------------------- /card-terminal-adapter/src/types.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © 2023 Jinwoo Park (pmnxis@gmail.com) 3 | * 4 | * SPDX-License-Identifier: MIT OR Apache-2.0 5 | */ 6 | 7 | //! # NDA feature + EEPROM (novella) feature related types. 8 | use static_assertions::*; 9 | use zeroable::Zeroable; 10 | 11 | #[derive(PartialEq, Eq, Clone, defmt::Format)] 12 | pub struct RawU24Price(pub [u8; 3]); 13 | 14 | impl From for RawU24Price { 15 | fn from(value: u32) -> Self { 16 | // big endianSlotPriceGameNum 17 | let value = value.min((1 << 24) - 1); 18 | 19 | Self([ 20 | ((value >> 16) & 0xFF) as u8, 21 | ((value >> 8) & 0xFF) as u8, 22 | (value & 0xFF) as u8, 23 | ]) 24 | } 25 | } 26 | 27 | impl From for u32 { 28 | fn from(value: RawU24Price) -> Self { 29 | // big endian 30 | ((value.0[0] as u32) << 16) | ((value.0[1] as u32) << 8) | (value.0[0] as u32) 31 | } 32 | } 33 | 34 | #[derive(Debug, Zeroable, defmt::Format, Clone, Eq, PartialEq, Ord, PartialOrd)] 35 | pub struct IncomeArcadeRequest { 36 | pub port: u8, 37 | pub pulse_count: u16, 38 | pub pulse_duration: u16, 39 | } 40 | 41 | /// [port: 4b, pulse_count: msb-4b], [pulse_count: 6b-lsb, pulse_duration: msb-2] 42 | /// [pulse_duration: 8b-lsb] 43 | #[derive(Clone, Zeroable, PartialEq, Eq)] 44 | pub struct RawU24IncomeArcade([u8; 3]); 45 | 46 | impl defmt::Format for RawU24IncomeArcade { 47 | fn format(&self, fmt: defmt::Formatter) { 48 | let raw_u24_income_arcade = IncomeArcadeRequest::from(self.clone()); 49 | defmt::write!(fmt, "{:?}", raw_u24_income_arcade); 50 | } 51 | } 52 | 53 | impl From for RawU24IncomeArcade { 54 | fn from(value: IncomeArcadeRequest) -> Self { 55 | let pulse_count = value.pulse_count.min(999); 56 | let pulse_duration = value.pulse_duration.min(999); 57 | 58 | Self([ 59 | (value.port << 4) | ((pulse_count >> 6) as u8 & 0xF), 60 | ((pulse_count as u8) << 2) | ((pulse_duration >> 8) as u8 & 0x3), 61 | pulse_duration as u8, 62 | ]) 63 | } 64 | } 65 | 66 | impl From for IncomeArcadeRequest { 67 | fn from(value: RawU24IncomeArcade) -> Self { 68 | Self { 69 | port: value.0[0] >> 4, 70 | pulse_count: (((value.0[0] & 0x0F) as u16) << 6) | (value.0[1] >> 2) as u16, 71 | pulse_duration: (((value.0[1] & 0x03) as u16) << 8) | value.0[2] as u16, 72 | } 73 | } 74 | } 75 | 76 | impl RawU24IncomeArcade { 77 | pub fn get_port_num(&self) -> u8 { 78 | self.0[0] >> 4 79 | } 80 | 81 | pub fn get_pulse_count(&self) -> u16 { 82 | (((self.0[0] & 0x0F) as u16) << 6) | (self.0[1] >> 2) as u16 83 | } 84 | } 85 | 86 | #[derive(PartialEq, Eq, Clone, Copy, defmt::Format)] 87 | pub struct RawPlayersInhibit { 88 | pub p1: bool, 89 | pub p2: bool, 90 | } 91 | 92 | #[repr(C)] 93 | #[derive(Clone, Zeroable, PartialEq, PartialOrd)] 94 | pub struct RawTerminalId { 95 | pub normal: [u8; 10], 96 | pub extend: [u8; 3], 97 | } 98 | assert_eq_size!(RawTerminalId, [u8; 13]); 99 | 100 | #[derive(Clone, Zeroable)] 101 | pub struct RawPortPulseCountDuration { 102 | pub inner: u32, 103 | } 104 | 105 | #[derive(defmt::Format)] 106 | pub struct SlotPriceGameNum { 107 | pub price: u32, 108 | pub game_num: u16, 109 | } 110 | 111 | #[derive(Clone, Zeroable)] 112 | pub struct RawU32SlotPriceGameNum(u32); 113 | 114 | impl From for RawU32SlotPriceGameNum { 115 | fn from(value: SlotPriceGameNum) -> Self { 116 | Self { 117 | 0: ((value.price.min(99_999) & ((1 << 17) - 1)) << 10) 118 | | ((value.game_num.min(999) & ((1 << 10) - 1)) as u32), 119 | } 120 | } 121 | } 122 | 123 | impl From for SlotPriceGameNum { 124 | fn from(value: RawU32SlotPriceGameNum) -> Self { 125 | Self { 126 | price: (value.0 >> 10) & ((1 << 17) - 1), 127 | game_num: (value.0 & ((1 << 10) - 1)) as u16, 128 | } 129 | } 130 | } 131 | 132 | impl defmt::Format for RawU32SlotPriceGameNum { 133 | fn format(&self, fmt: defmt::Formatter) { 134 | let degrade = SlotPriceGameNum::from(self.clone()); 135 | defmt::write!(fmt, "{:?}", degrade); 136 | } 137 | } 138 | 139 | impl RawU32SlotPriceGameNum { 140 | pub fn get_game_num(&self) -> u16 { 141 | (self.0 & ((1 << 10) - 1)) as u16 142 | } 143 | } 144 | 145 | #[repr(u8)] 146 | #[derive(Clone, Copy, Zeroable, PartialEq, PartialOrd, defmt::Format)] 147 | pub enum SlotProperty { 148 | Disabled, 149 | Enabled, 150 | TemporaryDisabled, 151 | } 152 | 153 | #[derive(Clone, Zeroable, defmt::Format)] 154 | pub struct RawCardPortBackup { 155 | // is enabled? 156 | pub property: SlotProperty, 157 | // Contains pulse count, pulse duration 158 | pub raw_extended: RawU32SlotPriceGameNum, 159 | pub raw_minimum: RawU24IncomeArcade, 160 | } 161 | assert_eq_size!(RawCardPortBackup, [u8; 8]); 162 | 163 | impl From<(SlotPriceGameNum, IncomeArcadeRequest)> for RawCardPortBackup { 164 | fn from((extended, minimum): (SlotPriceGameNum, IncomeArcadeRequest)) -> Self { 165 | Self { 166 | property: match extended.game_num { 167 | 0 => SlotProperty::Disabled, 168 | _ => SlotProperty::Enabled, 169 | }, 170 | raw_extended: extended.into(), 171 | raw_minimum: minimum.into(), 172 | } 173 | } 174 | } 175 | 176 | impl RawCardPortBackup { 177 | pub fn empty_slot() -> Self { 178 | Self::zeroed() 179 | } 180 | } 181 | 182 | #[derive(Clone, Zeroable, defmt::Format)] 183 | pub struct CardReaderPortBackup { 184 | pub raw_card_port_backup: [RawCardPortBackup; 4], 185 | } 186 | 187 | impl CardReaderPortBackup { 188 | pub fn empty_slot() -> Self { 189 | Self::zeroed() 190 | } 191 | 192 | pub fn is_zeroed(&self) -> bool { 193 | unsafe { 194 | let cast: &[u8] = core::slice::from_raw_parts( 195 | (self as *const _) as *const u8, 196 | core::mem::size_of::(), 197 | ); 198 | 199 | for each in cast { 200 | if *each != 0 { 201 | return false; 202 | } 203 | } 204 | } 205 | 206 | true 207 | } 208 | 209 | // 0 is player 1, <- this is temporary decide, 210 | // 1 is player 2, <- this is temporary decide, 211 | pub fn guess_player_by_port_num(&self, port_num: u8) -> u8 { 212 | for backup in &self.raw_card_port_backup { 213 | if backup.raw_minimum.get_port_num() == port_num { 214 | let game_num = backup.raw_extended.get_game_num(); 215 | 216 | return match (game_num, (game_num & 0x1) == 0x1) { 217 | (0, _) => 0, 218 | (_, true) => 0, 219 | (_, false) => 1, 220 | }; 221 | } 222 | } 223 | 0 224 | } 225 | 226 | // player 1 port is generally 1 227 | // player 2 port is generally 2 228 | pub fn guess_raw_income_by_player(&self, player: u8) -> Option<&RawU24IncomeArcade> { 229 | for backup in &self.raw_card_port_backup { 230 | if backup.property != SlotProperty::Enabled { 231 | continue; 232 | } 233 | 234 | let game_num = backup.raw_extended.get_game_num(); 235 | if (game_num == player as u16) || (game_num == (player + 2) as u16) { 236 | return Some(&backup.raw_minimum); 237 | } 238 | } 239 | 240 | None 241 | } 242 | 243 | // index should be u8 but to reduce size use u8. Index gurantee less than 256. 244 | pub fn guess_raw_income_index_by_player(&self, player: u8) -> Option { 245 | for (pos, backup) in self.raw_card_port_backup.iter().enumerate() { 246 | if backup.property != SlotProperty::Enabled { 247 | continue; 248 | } 249 | 250 | let game_num = backup.raw_extended.get_game_num(); 251 | if (game_num == player as u16) || (game_num == (player + 2) as u16) { 252 | return Some(pos as u8); 253 | } 254 | } 255 | 256 | None 257 | } 258 | 259 | pub fn set_inhibit(&mut self, inhibit: RawPlayersInhibit) { 260 | for i in 0..self.raw_card_port_backup.len() { 261 | let is_disabled = self.raw_card_port_backup[i].property == SlotProperty::Disabled; 262 | let right_side = (i & 1) == 0; 263 | let do_inhibit = if !right_side { inhibit.p1 } else { inhibit.p2 }; 264 | 265 | if !is_disabled { 266 | self.raw_card_port_backup[i].property = if do_inhibit { 267 | SlotProperty::TemporaryDisabled 268 | } else { 269 | SlotProperty::Enabled 270 | }; 271 | } 272 | } 273 | } 274 | } 275 | 276 | #[derive(Debug, Zeroable, defmt::Format, Clone, Eq, PartialEq, Ord, PartialOrd)] 277 | pub struct PulseStateRequest { 278 | pub port: u8, 279 | pub state: bool, 280 | } 281 | 282 | assert_eq_size!(CardReaderPortBackup, [u8; 32]); 283 | -------------------------------------------------------------------------------- /LICENSES/Apache-2.0.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2023 Jinwoo Park and other billmock-app-rs contributors 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. --------------------------------------------------------------------------------