├── .gitignore ├── main ├── .gitignore ├── memory.x ├── .cargo │ └── config ├── Cargo.toml ├── build.rs ├── Embed.toml ├── Cargo.lock └── src │ └── main.rs ├── video ├── .gitignore ├── memory.x ├── .cargo │ └── config ├── Cargo.toml ├── build.rs ├── Embed.toml ├── src │ ├── spi_slave │ │ └── mod.rs │ ├── main.rs │ └── video │ │ └── mod.rs └── Cargo.lock ├── desktop ├── .gitignore ├── Cargo.toml └── src │ └── main.rs ├── chf-emulator ├── .gitignore ├── roms │ └── place_roms_here.txt ├── testfiles │ └── test.bin ├── Cargo.lock ├── Cargo.toml └── src │ └── lib.rs ├── docs ├── schematic.png ├── schematic.sch └── schematic - Project.pdf ├── gen-test-bin ├── Cargo.toml ├── Cargo.lock └── src │ └── main.rs └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | **/target 2 | -------------------------------------------------------------------------------- /main/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /video/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /desktop/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /chf-emulator/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /docs/schematic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bjoernQ/channel-f-bluepill/main/docs/schematic.png -------------------------------------------------------------------------------- /docs/schematic.sch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bjoernQ/channel-f-bluepill/main/docs/schematic.sch -------------------------------------------------------------------------------- /chf-emulator/roms/place_roms_here.txt: -------------------------------------------------------------------------------- 1 | SL31253.bin 2 | SL31254.bin 3 | demo.bin (US demo cartridge) 4 | -------------------------------------------------------------------------------- /docs/schematic - Project.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bjoernQ/channel-f-bluepill/main/docs/schematic - Project.pdf -------------------------------------------------------------------------------- /chf-emulator/testfiles/test.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bjoernQ/channel-f-bluepill/main/chf-emulator/testfiles/test.bin -------------------------------------------------------------------------------- /main/memory.x: -------------------------------------------------------------------------------- 1 | /* Linker script for the STM32F103C8T6 */ 2 | MEMORY 3 | { 4 | FLASH : ORIGIN = 0x08000000, LENGTH = 128K 5 | RAM : ORIGIN = 0x20000000, LENGTH = 20K 6 | } -------------------------------------------------------------------------------- /video/memory.x: -------------------------------------------------------------------------------- 1 | /* Linker script for the STM32F103C8T6 */ 2 | MEMORY 3 | { 4 | FLASH : ORIGIN = 0x08000000, LENGTH = 128K 5 | RAM : ORIGIN = 0x20000000, LENGTH = 20K 6 | } -------------------------------------------------------------------------------- /chf-emulator/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "chf-emulator" 5 | version = "0.1.0" 6 | -------------------------------------------------------------------------------- /main/.cargo/config: -------------------------------------------------------------------------------- 1 | [target.thumbv7m-none-eabi] 2 | runner = 'probe-run --chip STM32F103CB' 3 | rustflags = [ 4 | "-C", "link-arg=-Tlink.x" 5 | ] 6 | [build] 7 | target = "thumbv7m-none-eabi" 8 | -------------------------------------------------------------------------------- /video/.cargo/config: -------------------------------------------------------------------------------- 1 | [target.thumbv7m-none-eabi] 2 | runner = 'probe-run --chip STM32F103CB' 3 | rustflags = [ 4 | "-C", "link-arg=-Tlink.x" 5 | ] 6 | [build] 7 | target = "thumbv7m-none-eabi" 8 | -------------------------------------------------------------------------------- /chf-emulator/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "chf-emulator" 3 | version = "0.1.0" 4 | authors = ["bjoern "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /gen-test-bin/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gen-test-bin" 3 | version = "0.1.0" 4 | authors = ["bjoern "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | fastrand = "1.4.0" 11 | -------------------------------------------------------------------------------- /desktop/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "desktop" 3 | version = "0.1.0" 4 | authors = ["bjoern "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | minifb = "0.19.1" 11 | rodio = "0.13.0" 12 | chf-emulator = { path = "../chf-emulator" } 13 | -------------------------------------------------------------------------------- /video/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "video" 3 | version = "0.1.0" 4 | authors = ["bjoern "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | cortex-m = "0.6.3" 11 | cortex-m-rt = "0.6.13" 12 | stm32f1xx-hal = { version = "0.6.1", features = ["rt", "stm32f103", "medium"] } 13 | embedded-hal = "0.2.4" 14 | rtt-target = { version = "0.2.2", features = ["cortex-m"] } 15 | panic-halt = "0.2.0" 16 | nb = "1.0.0" -------------------------------------------------------------------------------- /main/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "main" 3 | version = "0.1.0" 4 | authors = ["bjoern "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | cortex-m = "0.6.3" 11 | cortex-m-rt = "0.6.13" 12 | stm32f1xx-hal = { version = "0.6.1", features = ["rt", "stm32f103", "medium"] } 13 | embedded-hal = "0.2.4" 14 | rtt-target = { version = "0.3.0", features = ["cortex-m"] } 15 | panic-halt = "0.2.0" 16 | nb = "1.0.0" 17 | 18 | embedded-sdmmc = "0.3.0" 19 | 20 | chf-emulator = { path = "../chf-emulator" } 21 | -------------------------------------------------------------------------------- /main/build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::fs::File; 3 | use std::io::Write; 4 | use std::path::PathBuf; 5 | 6 | fn main() { 7 | // Put the linker script somewhere the linker can find it 8 | let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); 9 | File::create(out.join("memory.x")) 10 | .unwrap() 11 | .write_all(include_bytes!("memory.x")) 12 | .unwrap(); 13 | println!("cargo:rustc-link-search={}", out.display()); 14 | 15 | // Only re-run the build script when memory.x is changed, 16 | // instead of when any part of the source code changes. 17 | println!("cargo:rerun-if-changed=memory.x"); 18 | } 19 | -------------------------------------------------------------------------------- /video/build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::fs::File; 3 | use std::io::Write; 4 | use std::path::PathBuf; 5 | 6 | fn main() { 7 | // Put the linker script somewhere the linker can find it 8 | let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); 9 | File::create(out.join("memory.x")) 10 | .unwrap() 11 | .write_all(include_bytes!("memory.x")) 12 | .unwrap(); 13 | println!("cargo:rustc-link-search={}", out.display()); 14 | 15 | // Only re-run the build script when memory.x is changed, 16 | // instead of when any part of the source code changes. 17 | println!("cargo:rerun-if-changed=memory.x"); 18 | } 19 | -------------------------------------------------------------------------------- /gen-test-bin/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "cfg-if" 5 | version = "1.0.0" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 8 | 9 | [[package]] 10 | name = "fastrand" 11 | version = "1.4.0" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | checksum = "ca5faf057445ce5c9d4329e382b2ce7ca38550ef3b73a5348362d5f24e0c7fe3" 14 | dependencies = [ 15 | "instant", 16 | ] 17 | 18 | [[package]] 19 | name = "gen-test-bin" 20 | version = "0.1.0" 21 | dependencies = [ 22 | "fastrand", 23 | ] 24 | 25 | [[package]] 26 | name = "instant" 27 | version = "0.1.9" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" 30 | dependencies = [ 31 | "cfg-if", 32 | ] 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fairchild Channel F Emulator in Rust - Running on two STM32F103 2 | 3 | ## About 4 | 5 | This is a Fairchild Channel F emulator running on two STM32F103 BluePills connected via SPI. 6 | It reads games from an SD-card and outputs video and audio via SCART cable. 7 | 8 | While the Channel F wasn't really a great machine it's a remarkable step in history of video game consoles. 9 | 10 | If you want to learn more about the Channel F visit these links 11 | - https://en.wikipedia.org/wiki/Fairchild_Channel_F 12 | - https://www.fastcompany.com/3040889/the-untold-story-of-the-invention-of-the-game-cartridge 13 | - http://www.computinghistory.org.uk/det/5768/Adman-Grandstand-%28Fairchild-Channel-F%29-Video-Entertainment-Computer/ 14 | 15 | [![Alt text](https://img.youtube.com/vi/1F_543eXhB4/0.jpg)](https://www.youtube.com/watch?v=1F_543eXhB4) 16 | 17 | ## Sources used to implement the emulator 18 | 19 | - https://github.com/libretro/FreeChaF/blob/master/src/f8.c 20 | - https://www.lexaloffle.com/bbs/?pid=71273 21 | - http://channelf.se/veswiki/index.php?title=Main_Page 22 | - MAME 23 | 24 | ROMS found on archive.org 25 | 26 | It can run most games but some games don't work or have minor problems. (e.g. Maze doesn't work but I have no idea why) 27 | 28 | ## Code Organization 29 | 30 | |Directory|Contents| 31 | |---|---| 32 | |chf-emulator|the emulator core| 33 | |desktop|a desktop implementation of the emulator - just for testing - otherwise bad| 34 | |main|code running on the "main" MCU, compile in release mode, talks to the other MCU via MCU| 35 | |video|code running on the "video" MCU, must be compiled in release mode, get the pixel data from the other MCU via SPI| 36 | |gen-test-bin|generates a catrdige with random opcodes, I used MAME to generate a log to check in a unit test| 37 | 38 | ## Hardware 39 | 40 | The hardware is built from two BluePill STM32F103 modules, some resistors and a capacitor. 41 | 42 | Video output is done via a SCART cable. 43 | 44 | ![alt text](./docs/schematic.png "Schematic") 45 | 46 | Cartridges are stored on an sd-card - the reader is connected via SPI. 47 | 48 | The two MCUs communicate via SPI. 49 | -------------------------------------------------------------------------------- /main/Embed.toml: -------------------------------------------------------------------------------- 1 | [default.probe] 2 | # USB vendor ID 3 | # usb_vid = "1337" 4 | # USB product ID 5 | # usb_pid = "1337" 6 | # Serial number 7 | # serial = "12345678" 8 | # The protocol to be used for communicating with the target. 9 | protocol = "Swd" 10 | # The speed in kHz of the data link to the target. 11 | # speed = 1337 12 | 13 | [default.flashing] 14 | # Whether or not the target should be flashed. 15 | enabled = true 16 | # Whether or not the target should be halted after reset. 17 | # DEPRECATED, moved to reset section 18 | halt_afterwards = false 19 | # Whether or not bytes erased but not rewritten with data from the ELF 20 | # should be restored with their contents before erasing. 21 | restore_unwritten_bytes = false 22 | # The path where an SVG of the assembled flash layout should be written to. 23 | # flash_layout_output_path = "out.svg" 24 | 25 | [default.reset] 26 | # Whether or not the target should be reset. 27 | # When flashing is enabled as well, the target will be reset after flashing. 28 | enabled = true 29 | # Whether or not the target should be halted after reset. 30 | halt_afterwards = false 31 | 32 | [default.general] 33 | # The chip name of the chip to be debugged. 34 | chip = "STM32F103CB" 35 | # A list of chip descriptions to be loaded during runtime. 36 | chip_descriptions = [] 37 | # The default log level to be used. Possible values are one of: 38 | # "OFF", "ERROR", "WARN", "INFO", "DEBUG", "TRACE" 39 | log_level = "WARN" 40 | 41 | [default.rtt] 42 | # Whether or not an RTTUI should be opened after flashing. 43 | # This is exclusive and cannot be used with GDB at the moment. 44 | enabled = false 45 | # A list of channel associations to be displayed. If left empty, all channels are displayed. 46 | channels = [ 47 | # { up = 0, down = 0, name = "name", format = "String" } 48 | ] 49 | # The duration in ms for which the logger should retry to attach to RTT. 50 | timeout = 3000 51 | # Whether timestamps in the RTTUI are enabled 52 | show_timestamps = true 53 | # Whether to save rtt history buffer on exit. 54 | log_enabled = false 55 | # Where to save rtt history buffer relative to manifest path. 56 | log_path = "./logs" 57 | 58 | [default.gdb] 59 | # Whether or not a GDB server should be opened after flashing. 60 | # This is exclusive and cannot be used with RTT at the moment. 61 | enabled = true 62 | # The connection string in host:port format wher the GDB server will open a socket. 63 | gdb_connection_string = "0.0.0.0:3333" 64 | -------------------------------------------------------------------------------- /video/Embed.toml: -------------------------------------------------------------------------------- 1 | [default.probe] 2 | # USB vendor ID 3 | # usb_vid = "1337" 4 | # USB product ID 5 | # usb_pid = "1337" 6 | # Serial number 7 | # serial = "12345678" 8 | # The protocol to be used for communicating with the target. 9 | protocol = "Swd" 10 | # The speed in kHz of the data link to the target. 11 | # speed = 1337 12 | 13 | [default.flashing] 14 | # Whether or not the target should be flashed. 15 | enabled = true 16 | # Whether or not the target should be halted after reset. 17 | # DEPRECATED, moved to reset section 18 | halt_afterwards = false 19 | # Whether or not bytes erased but not rewritten with data from the ELF 20 | # should be restored with their contents before erasing. 21 | restore_unwritten_bytes = false 22 | # The path where an SVG of the assembled flash layout should be written to. 23 | # flash_layout_output_path = "out.svg" 24 | 25 | [default.reset] 26 | # Whether or not the target should be reset. 27 | # When flashing is enabled as well, the target will be reset after flashing. 28 | enabled = true 29 | # Whether or not the target should be halted after reset. 30 | halt_afterwards = false 31 | 32 | [default.general] 33 | # The chip name of the chip to be debugged. 34 | chip = "STM32F103CB" 35 | # A list of chip descriptions to be loaded during runtime. 36 | chip_descriptions = [] 37 | # The default log level to be used. Possible values are one of: 38 | # "OFF", "ERROR", "WARN", "INFO", "DEBUG", "TRACE" 39 | log_level = "WARN" 40 | 41 | [default.rtt] 42 | # Whether or not an RTTUI should be opened after flashing. 43 | # This is exclusive and cannot be used with GDB at the moment. 44 | enabled = false 45 | # A list of channel associations to be displayed. If left empty, all channels are displayed. 46 | channels = [ 47 | # { up = 0, down = 0, name = "name", format = "String" } 48 | ] 49 | # The duration in ms for which the logger should retry to attach to RTT. 50 | timeout = 3000 51 | # Whether timestamps in the RTTUI are enabled 52 | show_timestamps = true 53 | # Whether to save rtt history buffer on exit. 54 | log_enabled = false 55 | # Where to save rtt history buffer relative to manifest path. 56 | log_path = "./logs" 57 | 58 | [default.gdb] 59 | # Whether or not a GDB server should be opened after flashing. 60 | # This is exclusive and cannot be used with RTT at the moment. 61 | enabled = true 62 | # The connection string in host:port format wher the GDB server will open a socket. 63 | gdb_connection_string = "0.0.0.0:3333" 64 | -------------------------------------------------------------------------------- /video/src/spi_slave/mod.rs: -------------------------------------------------------------------------------- 1 | use core::{ops::Deref, ptr}; 2 | 3 | use embedded_hal::spi::{Mode, Phase, Polarity}; 4 | use stm32f1xx_hal::{ 5 | gpio::{ 6 | gpioa::{PA4, PA5, PA6, PA7}, 7 | Alternate, Floating, Input, PullDown, PushPull, 8 | }, 9 | rcc::{Enable, GetBusFreq, Reset}, 10 | spi::{Error, SpiRegisterBlock}, 11 | }; 12 | 13 | pub struct Spi1Slave { 14 | spi: SPI, 15 | _pins: ( 16 | PA4>, 17 | PA5>, 18 | PA6>, 19 | PA7>, 20 | ), 21 | } 22 | 23 | impl Spi1Slave 24 | where 25 | SPI: Deref + Enable + Reset, 26 | { 27 | /** 28 | Constructs an SPI instance using SPI1. 29 | 30 | The pin parameter tuple (nss, sck, miso, mosi) should be `(PA4, PA5, PA6, PA7)` configured as `(Input, Alternate, Alternate, Input)`. 31 | */ 32 | pub fn spi1slave( 33 | spi: SPI, 34 | pins: ( 35 | PA4>, 36 | PA5>, 37 | PA6>, 38 | PA7>, 39 | ), 40 | mode: Mode, 41 | apb: &mut SPI::Bus, 42 | ) -> Self 43 | where 44 | SPI::Bus: GetBusFreq, 45 | { 46 | // enable or reset SPI 47 | SPI::enable(apb); 48 | SPI::reset(apb); 49 | 50 | // disable SS output 51 | spi.cr2.write(|w| w.ssoe().clear_bit()); 52 | 53 | spi.cr1.write(|w| { 54 | w 55 | // clock phase from config 56 | .cpha() 57 | .bit(mode.phase == Phase::CaptureOnSecondTransition) 58 | // clock polarity from config 59 | .cpol() 60 | .bit(mode.polarity == Polarity::IdleHigh) 61 | // mstr: slave configuration 62 | .mstr() 63 | .clear_bit() 64 | // baudrate not valid for slave 65 | // lsbfirst: MSB first 66 | .lsbfirst() 67 | .clear_bit() 68 | // ssm: enable hw NSS control 69 | .ssm() 70 | .clear_bit() 71 | // ssi: set nss high = master mode 72 | .ssi() 73 | .set_bit() 74 | // dff: 8 bit frames 75 | .dff() 76 | .clear_bit() 77 | // bidimode: 2-line unidirectional 78 | .bidimode() 79 | .clear_bit() 80 | // both TX and RX are used 81 | .rxonly() 82 | .clear_bit() 83 | // spe: enable the SPI bus 84 | .spe() 85 | .set_bit() 86 | }); 87 | 88 | Spi1Slave { spi, _pins: pins } 89 | } 90 | 91 | pub fn clear_ovr(&self) { 92 | let _v = unsafe { ptr::read_volatile(&self.spi.dr as *const _ as *const u8) }; 93 | self.spi.sr.read(); 94 | } 95 | } 96 | 97 | impl embedded_hal::spi::FullDuplex for Spi1Slave 98 | where 99 | SPI: Deref, 100 | { 101 | type Error = Error; 102 | 103 | fn read(&mut self) -> nb::Result { 104 | let sr = self.spi.sr.read(); 105 | 106 | Err(if sr.ovr().bit_is_set() { 107 | nb::Error::Other(Error::Overrun) 108 | } else if sr.modf().bit_is_set() { 109 | nb::Error::Other(Error::ModeFault) 110 | } else if sr.crcerr().bit_is_set() { 111 | nb::Error::Other(Error::Crc) 112 | } else if sr.rxne().bit_is_set() { 113 | // NOTE(read_volatile) read only 1 byte (the svd2rust API only allows 114 | // reading a half-word) 115 | return Ok(unsafe { ptr::read_volatile(&self.spi.dr as *const _ as *const u8) }); 116 | } else { 117 | nb::Error::WouldBlock 118 | }) 119 | } 120 | 121 | fn send(&mut self, byte: u8) -> nb::Result<(), Error> { 122 | let sr = self.spi.sr.read(); 123 | 124 | Err(if sr.ovr().bit_is_set() { 125 | nb::Error::Other(Error::Overrun) 126 | } else if sr.modf().bit_is_set() { 127 | nb::Error::Other(Error::ModeFault) 128 | } else if sr.crcerr().bit_is_set() { 129 | nb::Error::Other(Error::Crc) 130 | } else if sr.txe().bit_is_set() { 131 | // NOTE(write_volatile) see note above 132 | unsafe { ptr::write_volatile(&self.spi.dr as *const _ as *mut u8, byte) } 133 | return Ok(()); 134 | } else { 135 | nb::Error::WouldBlock 136 | }) 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /gen-test-bin/src/main.rs: -------------------------------------------------------------------------------- 1 | // opcodes we want to test 2 | // not branching, not in, not out 3 | struct Opcode(u8, usize); 4 | 5 | const OPCODES_TO_TEST: [Opcode; 181] = [ 6 | Opcode(0x00, 0), 7 | Opcode(0x01, 0), 8 | Opcode(0x02, 0), 9 | Opcode(0x03, 0), 10 | Opcode(0x04, 0), 11 | Opcode(0x05, 0), 12 | Opcode(0x06, 0), 13 | Opcode(0x07, 0), 14 | Opcode(0x08, 0), 15 | Opcode(0x09, 0), 16 | Opcode(0x0a, 0), 17 | Opcode(0x0b, 0), 18 | Opcode(0x0e, 0), 19 | Opcode(0x0f, 0), 20 | Opcode(0x10, 0), 21 | Opcode(0x11, 0), 22 | Opcode(0x12, 0), 23 | Opcode(0x13, 0), 24 | Opcode(0x14, 0), 25 | Opcode(0x15, 0), 26 | Opcode(0x16, 0), 27 | // Opcode(0x17, 0), // no write 28 | Opcode(0x18, 0), 29 | Opcode(0x19, 0), 30 | Opcode(0x1a, 0), 31 | Opcode(0x1b, 0), 32 | Opcode(0x1d, 0), 33 | Opcode(0x1e, 0), 34 | Opcode(0x1f, 0), 35 | Opcode(0x20, 1), 36 | Opcode(0x21, 1), 37 | Opcode(0x22, 1), 38 | Opcode(0x23, 1), 39 | Opcode(0x24, 1), 40 | Opcode(0x25, 1), 41 | Opcode(0x2b, 0), 42 | Opcode(0x2c, 0), 43 | Opcode(0x30, 0), 44 | Opcode(0x31, 0), 45 | Opcode(0x32, 0), 46 | Opcode(0x33, 0), 47 | Opcode(0x34, 0), 48 | Opcode(0x35, 0), 49 | Opcode(0x36, 0), 50 | Opcode(0x37, 0), 51 | Opcode(0x38, 0), 52 | Opcode(0x39, 0), 53 | Opcode(0x3a, 0), 54 | Opcode(0x3b, 0), 55 | Opcode(0x3c, 0), 56 | Opcode(0x3d, 0), 57 | Opcode(0x3e, 0), 58 | Opcode(0x40, 0), 59 | Opcode(0x41, 0), 60 | Opcode(0x42, 0), 61 | Opcode(0x43, 0), 62 | Opcode(0x44, 0), 63 | Opcode(0x45, 0), 64 | Opcode(0x46, 0), 65 | Opcode(0x47, 0), 66 | Opcode(0x48, 0), 67 | Opcode(0x49, 0), 68 | Opcode(0x4a, 0), 69 | Opcode(0x4b, 0), 70 | Opcode(0x4c, 0), 71 | Opcode(0x4d, 0), 72 | Opcode(0x4e, 0), 73 | Opcode(0x50, 0), 74 | Opcode(0x51, 0), 75 | Opcode(0x52, 0), 76 | Opcode(0x53, 0), 77 | Opcode(0x54, 0), 78 | Opcode(0x55, 0), 79 | Opcode(0x56, 0), 80 | Opcode(0x57, 0), 81 | Opcode(0x58, 0), 82 | Opcode(0x59, 0), 83 | Opcode(0x5a, 0), 84 | Opcode(0x5b, 0), 85 | Opcode(0x5c, 0), 86 | Opcode(0x5d, 0), 87 | Opcode(0x5e, 0), 88 | Opcode(0x60, 0), 89 | Opcode(0x61, 0), 90 | Opcode(0x62, 0), 91 | Opcode(0x63, 0), 92 | Opcode(0x64, 0), 93 | Opcode(0x65, 0), 94 | Opcode(0x66, 0), 95 | Opcode(0x67, 0), 96 | Opcode(0x68, 0), 97 | Opcode(0x69, 0), 98 | Opcode(0x6a, 0), 99 | Opcode(0x6b, 0), 100 | Opcode(0x6c, 0), 101 | Opcode(0x6d, 0), 102 | Opcode(0x6e, 0), 103 | Opcode(0x6f, 0), 104 | Opcode(0x70, 0), 105 | Opcode(0x71, 0), 106 | Opcode(0x72, 0), 107 | Opcode(0x73, 0), 108 | Opcode(0x74, 0), 109 | Opcode(0x75, 0), 110 | Opcode(0x76, 0), 111 | Opcode(0x77, 0), 112 | Opcode(0x78, 0), 113 | Opcode(0x79, 0), 114 | Opcode(0x7a, 0), 115 | Opcode(0x7b, 0), 116 | Opcode(0x7c, 0), 117 | Opcode(0x7d, 0), 118 | Opcode(0x7e, 0), 119 | Opcode(0x7f, 0), 120 | Opcode(0x88, 0), 121 | Opcode(0x89, 0), 122 | Opcode(0x8a, 0), 123 | Opcode(0x8b, 0), 124 | Opcode(0x8c, 0), 125 | Opcode(0x8d, 0), 126 | Opcode(0x8e, 0), 127 | Opcode(0xc0, 0), 128 | Opcode(0xc1, 0), 129 | Opcode(0xc2, 0), 130 | Opcode(0xc3, 0), 131 | Opcode(0xc4, 0), 132 | Opcode(0xc5, 0), 133 | Opcode(0xc6, 0), 134 | Opcode(0xc7, 0), 135 | Opcode(0xc8, 0), 136 | Opcode(0xc9, 0), 137 | Opcode(0xca, 0), 138 | Opcode(0xcb, 0), 139 | Opcode(0xcc, 0), 140 | Opcode(0xcd, 0), 141 | Opcode(0xce, 0), 142 | Opcode(0xd0, 0), 143 | Opcode(0xd1, 0), 144 | Opcode(0xd2, 0), 145 | Opcode(0xd3, 0), 146 | Opcode(0xd4, 0), 147 | Opcode(0xd5, 0), 148 | Opcode(0xd6, 0), 149 | Opcode(0xd7, 0), 150 | Opcode(0xd8, 0), 151 | Opcode(0xd9, 0), 152 | Opcode(0xda, 0), 153 | Opcode(0xdb, 0), 154 | Opcode(0xdc, 0), 155 | Opcode(0xdd, 0), 156 | Opcode(0xde, 0), 157 | Opcode(0xe0, 0), 158 | Opcode(0xe1, 0), 159 | Opcode(0xe1, 0), 160 | Opcode(0xe2, 0), 161 | Opcode(0xe3, 0), 162 | Opcode(0xe4, 0), 163 | Opcode(0xe5, 0), 164 | Opcode(0xe6, 0), 165 | Opcode(0xe7, 0), 166 | Opcode(0xe8, 0), 167 | Opcode(0xe9, 0), 168 | Opcode(0xea, 0), 169 | Opcode(0xeb, 0), 170 | Opcode(0xec, 0), 171 | Opcode(0xed, 0), 172 | Opcode(0xee, 0), 173 | Opcode(0xf0, 0), 174 | Opcode(0xf1, 0), 175 | Opcode(0xf2, 0), 176 | Opcode(0xf3, 0), 177 | Opcode(0xf4, 0), 178 | Opcode(0xf5, 0), 179 | Opcode(0xf6, 0), 180 | Opcode(0xf7, 0), 181 | Opcode(0xf8, 0), 182 | Opcode(0xf9, 0), 183 | Opcode(0xfa, 0), 184 | Opcode(0xfb, 0), 185 | Opcode(0xfc, 0), 186 | Opcode(0xfd, 0), 187 | Opcode(0xfe, 0), 188 | ]; 189 | 190 | // name the binary as maze.bin, use "mame -debug channelf maze" 191 | // in mame debugger use this to trace 192 | // bpset 1000 193 | // trace test.log,0,noloop,{tracelog "A=%02X W=%02X IS=%02X R0=%02X R1=%02X R2=%02X R3=%02X R4=%02X ", a, w, is, r0,r1,r2,r3,r4} 194 | 195 | fn main() { 196 | let mut binary = [0u8; 2 * 1024]; 197 | 198 | for i in 0..binary.len() { 199 | binary[i] = OPCODES_TO_TEST[fastrand::usize(..OPCODES_TO_TEST.len())].0; 200 | } 201 | 202 | let mut i = 2usize; 203 | loop { 204 | let opcode = &OPCODES_TO_TEST[fastrand::usize(..OPCODES_TO_TEST.len())]; 205 | 206 | if i + 1 + opcode.1 <= binary.len() { 207 | binary[i] = opcode.0; 208 | i += 1; 209 | 210 | for _ in 0..opcode.1 { 211 | binary[i] = fastrand::u8(0..255); 212 | i += 1; 213 | } 214 | } else { 215 | continue; 216 | } 217 | 218 | if i >= binary.len() { 219 | break; 220 | } 221 | } 222 | 223 | binary[0] = 0x55; 224 | binary[1] = 0x00; 225 | 226 | std::fs::write("./test.bin", &binary).unwrap(); 227 | } 228 | -------------------------------------------------------------------------------- /video/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | #![feature(asm)] 4 | 5 | use nb::block; 6 | use panic_halt as _; 7 | use rtt_target::rtt_init_print; 8 | 9 | use cortex_m_rt::entry; 10 | 11 | use spi_slave::Spi1Slave; 12 | use stm32f1xx_hal::{ 13 | pac::{self}, 14 | prelude::*, 15 | }; 16 | 17 | use embedded_hal::{ 18 | digital::v2::OutputPin, 19 | spi::{Mode, Phase, Polarity}, 20 | }; 21 | use video::VID_RAM; 22 | 23 | mod spi_slave; 24 | mod video; 25 | 26 | const COLORS: [u8; 16] = [ 27 | 0b000000, 0b111111, 0b111111, 0b111111, // 28 | 0b101011, 0b000011, 0b110000, 0b001000, // 29 | 0b010101, 0b000011, 0b110000, 0b001000, // 30 | 0b011101, 0b000011, 0b110000, 0b001000, // 31 | ]; 32 | 33 | #[entry] 34 | fn main() -> ! { 35 | let mut indexed_pixels = [0u8; 128 * 64]; 36 | rtt_init_print!(); 37 | 38 | // Get access to the core peripherals from the cortex-m crate 39 | let mut cp = cortex_m::Peripherals::take().unwrap(); 40 | // Get access to the device specific peripherals from the peripheral access crate 41 | let dp = pac::Peripherals::take().unwrap(); 42 | 43 | // Take ownership over the raw flash and rcc devices and convert them into the corresponding 44 | // HAL structs 45 | let mut flash = dp.FLASH.constrain(); 46 | let mut rcc = dp.RCC.constrain(); 47 | 48 | let _clocks = rcc 49 | .cfgr 50 | .use_hse(8.mhz()) 51 | .sysclk(72.mhz()) 52 | .pclk1(36.mhz()) 53 | .pclk2(72.mhz()) 54 | .freeze(&mut flash.acr); 55 | 56 | let mut afio = dp.AFIO.constrain(&mut rcc.apb2); 57 | let mut gpioa = dp.GPIOA.split(&mut rcc.apb2); 58 | let mut gpiob = dp.GPIOB.split(&mut rcc.apb2); 59 | let (_pa15, pb3, pb4) = afio.mapr.disable_jtag(gpioa.pa15, gpiob.pb3, gpiob.pb4); 60 | 61 | // configure video pins 62 | let _pb3 = pb3.into_push_pull_output(&mut gpiob.crl); 63 | let _pb4 = pb4.into_push_pull_output(&mut gpiob.crl); 64 | let _pb5 = gpiob.pb5.into_push_pull_output(&mut gpiob.crl); 65 | let _pb6 = gpiob.pb6.into_push_pull_output(&mut gpiob.crl); 66 | let _pb7 = gpiob.pb7.into_push_pull_output(&mut gpiob.crl); 67 | let _pb8 = gpiob.pb8.into_push_pull_output(&mut gpiob.crh); 68 | let _pb9 = gpiob.pb9.into_alternate_push_pull(&mut gpiob.crh); // timer controlled 69 | 70 | // for testing use the onboard led 71 | let mut gpioc = dp.GPIOC.split(&mut rcc.apb2); 72 | let mut led = gpioc.pc13.into_push_pull_output(&mut gpioc.crh); 73 | 74 | // PA0 is used to signal busy 75 | let busy = gpioa.pa0.into_push_pull_output(&mut gpioa.crl); 76 | 77 | led.set_high().unwrap_or_default(); // on board LED off 78 | 79 | // prepare video stuff 80 | video::init_video(&mut cp, dp.TIM4, dp.TIM1, busy); 81 | 82 | // start video output 83 | video::start_video(); 84 | 85 | // fill black into the vid-ram 86 | for i in 0..(64 * 128) { 87 | unsafe { 88 | VID_RAM[i as usize] = 0; 89 | } 90 | } 91 | 92 | let pins = ( 93 | gpioa.pa4.into_pull_down_input(&mut gpioa.crl), // NSS 94 | gpioa.pa5.into_alternate_push_pull(&mut gpioa.crl), // SCK 95 | gpioa.pa6.into_alternate_push_pull(&mut gpioa.crl), // MISO 96 | gpioa.pa7.into_floating_input(&mut gpioa.crl), // MOSI 97 | ); 98 | 99 | let spi_mode = Mode { 100 | polarity: Polarity::IdleLow, 101 | phase: Phase::CaptureOnFirstTransition, 102 | }; 103 | let mut spi = Spi1Slave::spi1slave(dp.SPI1, pins, spi_mode, &mut rcc.apb2); 104 | 105 | spi.send(0x7f).unwrap(); 106 | spi.clear_ovr(); 107 | 108 | let mut d; 109 | 110 | // XXXXXXXY YYYYYCCC 111 | 112 | let mut z = 0u8; 113 | let mut last_byte = 0u8; 114 | let mut was_ovr = false; 115 | 116 | let mut cnt = 0; 117 | 118 | loop { 119 | cnt += 1; 120 | if cnt >= 5000 { 121 | cnt = 0; 122 | led.toggle().unwrap(); 123 | } 124 | 125 | let snd_byte = if !was_ovr { 0x7f } else { 0xff }; 126 | 127 | let res = block!(spi.send(snd_byte)); 128 | if let Err(e) = res { 129 | match e { 130 | stm32f1xx_hal::spi::Error::Overrun => { 131 | spi.clear_ovr(); 132 | continue; 133 | } 134 | stm32f1xx_hal::spi::Error::ModeFault => {} 135 | stm32f1xx_hal::spi::Error::Crc => {} 136 | stm32f1xx_hal::spi::Error::_Extensible => {} 137 | } 138 | } 139 | 140 | let res = block!(spi.read()); 141 | 142 | d = match res { 143 | Ok(v) => { 144 | was_ovr = false; 145 | v 146 | } 147 | Err(e) => { 148 | was_ovr = true; 149 | match e { 150 | stm32f1xx_hal::spi::Error::Overrun => { 151 | spi.clear_ovr(); 152 | } 153 | stm32f1xx_hal::spi::Error::ModeFault => {} 154 | stm32f1xx_hal::spi::Error::Crc => {} 155 | stm32f1xx_hal::spi::Error::_Extensible => {} 156 | } 157 | 255 158 | } 159 | }; 160 | 161 | if z == 1 && !was_ovr { 162 | let x = (last_byte & 0b11111110) >> 1; 163 | let y = ((last_byte & 1) << 5) | ((d & 0b11111000) >> 3); 164 | let mut c = d & 0b111; 165 | 166 | if x == 125 || x == 126 { 167 | handle_palette_change(y, x, c, &mut indexed_pixels); 168 | } else { 169 | let palette = unsafe { 170 | ((VID_RAM[y as usize * 128usize + 125usize] & 2 >> 1) 171 | | (VID_RAM[y as usize * 128usize + 126usize])) 172 | & 0b11 173 | } as usize; 174 | c = COLORS[c as usize + palette * 4usize]; 175 | 176 | let offset = y as usize * 128usize + x as usize; 177 | indexed_pixels[offset] = d & 0b111; 178 | unsafe { 179 | VID_RAM[offset] = c; 180 | } 181 | } 182 | 183 | z = 0; 184 | } else if !was_ovr && z == 0 { 185 | last_byte = d; 186 | z = z + 1; 187 | } 188 | } 189 | } 190 | 191 | fn handle_palette_change(y: u8, x: u8, new_palette_value: u8, indexed_pixels: &mut [u8]) { 192 | let current_palette = unsafe { 193 | ((VID_RAM[y as usize * 128usize + 125usize] & 2 >> 1) 194 | | (VID_RAM[y as usize * 128usize + 126usize])) 195 | & 0b11 196 | } as usize; 197 | 198 | let new_palette = if x == 125 { 199 | unsafe { 200 | ((new_palette_value & 2 >> 1) | (VID_RAM[y as usize * 128usize + 126usize])) & 0b11 201 | } 202 | } else { 203 | unsafe { 204 | ((VID_RAM[y as usize * 128usize + 125usize] & 2 >> 1) | (new_palette_value)) & 0b11 205 | } 206 | } as usize; 207 | 208 | if current_palette == new_palette { 209 | return; 210 | } 211 | 212 | for xx in 0..102 { 213 | unsafe { 214 | VID_RAM[y as usize * 128usize + xx] = 215 | COLORS[new_palette * 4 + indexed_pixels[xx + y as usize * 128usize] as usize]; 216 | } 217 | } 218 | 219 | let offset = y as usize * 128usize + x as usize; 220 | indexed_pixels[offset] = new_palette_value; 221 | unsafe { 222 | VID_RAM[offset] = new_palette_value; 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /video/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "aligned" 5 | version = "0.3.4" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "c19796bd8d477f1a9d4ac2465b464a8b1359474f06a96bb3cda650b4fca309bf" 8 | dependencies = [ 9 | "as-slice", 10 | ] 11 | 12 | [[package]] 13 | name = "as-slice" 14 | version = "0.1.4" 15 | source = "registry+https://github.com/rust-lang/crates.io-index" 16 | checksum = "bb4d1c23475b74e3672afa8c2be22040b8b7783ad9b461021144ed10a46bb0e6" 17 | dependencies = [ 18 | "generic-array 0.12.3", 19 | "generic-array 0.13.2", 20 | "generic-array 0.14.4", 21 | "stable_deref_trait", 22 | ] 23 | 24 | [[package]] 25 | name = "bare-metal" 26 | version = "0.2.5" 27 | source = "registry+https://github.com/rust-lang/crates.io-index" 28 | checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" 29 | dependencies = [ 30 | "rustc_version", 31 | ] 32 | 33 | [[package]] 34 | name = "bitfield" 35 | version = "0.13.2" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" 38 | 39 | [[package]] 40 | name = "cast" 41 | version = "0.2.3" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "4b9434b9a5aa1450faa3f9cb14ea0e8c53bb5d2b3c1bfd1ab4fc03e9f33fbfb0" 44 | dependencies = [ 45 | "rustc_version", 46 | ] 47 | 48 | [[package]] 49 | name = "cortex-m" 50 | version = "0.6.4" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | checksum = "88cdafeafba636c00c467ded7f1587210725a1adfab0c24028a7844b87738263" 53 | dependencies = [ 54 | "aligned", 55 | "bare-metal", 56 | "bitfield", 57 | "volatile-register", 58 | ] 59 | 60 | [[package]] 61 | name = "cortex-m-rt" 62 | version = "0.6.13" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | checksum = "980c9d0233a909f355ed297ef122f257942de5e0a2cb1c39f60684b65bcb90fb" 65 | dependencies = [ 66 | "cortex-m-rt-macros", 67 | "r0", 68 | ] 69 | 70 | [[package]] 71 | name = "cortex-m-rt-macros" 72 | version = "0.1.8" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "4717562afbba06e760d34451919f5c3bf3ac15c7bb897e8b04862a7428378647" 75 | dependencies = [ 76 | "proc-macro2", 77 | "quote", 78 | "syn", 79 | ] 80 | 81 | [[package]] 82 | name = "embedded-hal" 83 | version = "0.2.4" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "fa998ce59ec9765d15216393af37a58961ddcefb14c753b4816ba2191d865fcb" 86 | dependencies = [ 87 | "nb 0.1.3", 88 | "void", 89 | ] 90 | 91 | [[package]] 92 | name = "generic-array" 93 | version = "0.12.3" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" 96 | dependencies = [ 97 | "typenum", 98 | ] 99 | 100 | [[package]] 101 | name = "generic-array" 102 | version = "0.13.2" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "0ed1e761351b56f54eb9dcd0cfaca9fd0daecf93918e1cfc01c8a3d26ee7adcd" 105 | dependencies = [ 106 | "typenum", 107 | ] 108 | 109 | [[package]] 110 | name = "generic-array" 111 | version = "0.14.4" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" 114 | dependencies = [ 115 | "typenum", 116 | "version_check", 117 | ] 118 | 119 | [[package]] 120 | name = "nb" 121 | version = "0.1.3" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" 124 | dependencies = [ 125 | "nb 1.0.0", 126 | ] 127 | 128 | [[package]] 129 | name = "nb" 130 | version = "1.0.0" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "546c37ac5d9e56f55e73b677106873d9d9f5190605e41a856503623648488cae" 133 | 134 | [[package]] 135 | name = "panic-halt" 136 | version = "0.2.0" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "de96540e0ebde571dc55c73d60ef407c653844e6f9a1e2fdbd40c07b9252d812" 139 | 140 | [[package]] 141 | name = "proc-macro2" 142 | version = "1.0.24" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" 145 | dependencies = [ 146 | "unicode-xid", 147 | ] 148 | 149 | [[package]] 150 | name = "quote" 151 | version = "1.0.8" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df" 154 | dependencies = [ 155 | "proc-macro2", 156 | ] 157 | 158 | [[package]] 159 | name = "r0" 160 | version = "0.2.2" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "e2a38df5b15c8d5c7e8654189744d8e396bddc18ad48041a500ce52d6948941f" 163 | 164 | [[package]] 165 | name = "rtt-target" 166 | version = "0.2.2" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "0869b4c5b6a6d8c5583fc473f9eb3423a170f77626b8c8a7fb18eddcda5770e2" 169 | dependencies = [ 170 | "cortex-m", 171 | "ufmt-write", 172 | ] 173 | 174 | [[package]] 175 | name = "rustc_version" 176 | version = "0.2.3" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" 179 | dependencies = [ 180 | "semver", 181 | ] 182 | 183 | [[package]] 184 | name = "semver" 185 | version = "0.9.0" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" 188 | dependencies = [ 189 | "semver-parser", 190 | ] 191 | 192 | [[package]] 193 | name = "semver-parser" 194 | version = "0.7.0" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 197 | 198 | [[package]] 199 | name = "stable_deref_trait" 200 | version = "1.2.0" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 203 | 204 | [[package]] 205 | name = "stm32f1" 206 | version = "0.11.0" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "849b1e8d9bcfd792c9d9178cf86165d299a661c26e35d9322ae9382d3f3fe460" 209 | dependencies = [ 210 | "bare-metal", 211 | "cortex-m", 212 | "cortex-m-rt", 213 | "vcell", 214 | ] 215 | 216 | [[package]] 217 | name = "stm32f1xx-hal" 218 | version = "0.6.1" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "af9b9e5d7c2901ee39fc9527412327a1fe08f1d84e9d7f4b3497448e655e5098" 221 | dependencies = [ 222 | "as-slice", 223 | "cast", 224 | "cortex-m", 225 | "cortex-m-rt", 226 | "embedded-hal", 227 | "nb 0.1.3", 228 | "stm32f1", 229 | "void", 230 | ] 231 | 232 | [[package]] 233 | name = "syn" 234 | version = "1.0.57" 235 | source = "registry+https://github.com/rust-lang/crates.io-index" 236 | checksum = "4211ce9909eb971f111059df92c45640aad50a619cf55cd76476be803c4c68e6" 237 | dependencies = [ 238 | "proc-macro2", 239 | "quote", 240 | "unicode-xid", 241 | ] 242 | 243 | [[package]] 244 | name = "typenum" 245 | version = "1.12.0" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" 248 | 249 | [[package]] 250 | name = "ufmt-write" 251 | version = "0.1.0" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "e87a2ed6b42ec5e28cc3b94c09982969e9227600b2e3dcbc1db927a84c06bd69" 254 | 255 | [[package]] 256 | name = "unicode-xid" 257 | version = "0.2.1" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" 260 | 261 | [[package]] 262 | name = "vcell" 263 | version = "0.1.3" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" 266 | 267 | [[package]] 268 | name = "version_check" 269 | version = "0.9.2" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" 272 | 273 | [[package]] 274 | name = "video" 275 | version = "0.1.0" 276 | dependencies = [ 277 | "cortex-m", 278 | "cortex-m-rt", 279 | "embedded-hal", 280 | "nb 1.0.0", 281 | "panic-halt", 282 | "rtt-target", 283 | "stm32f1xx-hal", 284 | ] 285 | 286 | [[package]] 287 | name = "void" 288 | version = "1.0.2" 289 | source = "registry+https://github.com/rust-lang/crates.io-index" 290 | checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" 291 | 292 | [[package]] 293 | name = "volatile-register" 294 | version = "0.2.0" 295 | source = "registry+https://github.com/rust-lang/crates.io-index" 296 | checksum = "0d67cb4616d99b940db1d6bd28844ff97108b498a6ca850e5b6191a532063286" 297 | dependencies = [ 298 | "vcell", 299 | ] 300 | -------------------------------------------------------------------------------- /main/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "aligned" 5 | version = "0.3.4" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "c19796bd8d477f1a9d4ac2465b464a8b1359474f06a96bb3cda650b4fca309bf" 8 | dependencies = [ 9 | "as-slice", 10 | ] 11 | 12 | [[package]] 13 | name = "as-slice" 14 | version = "0.1.4" 15 | source = "registry+https://github.com/rust-lang/crates.io-index" 16 | checksum = "bb4d1c23475b74e3672afa8c2be22040b8b7783ad9b461021144ed10a46bb0e6" 17 | dependencies = [ 18 | "generic-array 0.12.3", 19 | "generic-array 0.13.2", 20 | "generic-array 0.14.4", 21 | "stable_deref_trait", 22 | ] 23 | 24 | [[package]] 25 | name = "bare-metal" 26 | version = "0.2.5" 27 | source = "registry+https://github.com/rust-lang/crates.io-index" 28 | checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" 29 | dependencies = [ 30 | "rustc_version", 31 | ] 32 | 33 | [[package]] 34 | name = "bitfield" 35 | version = "0.13.2" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" 38 | 39 | [[package]] 40 | name = "byteorder" 41 | version = "1.4.2" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b" 44 | 45 | [[package]] 46 | name = "cast" 47 | version = "0.2.3" 48 | source = "registry+https://github.com/rust-lang/crates.io-index" 49 | checksum = "4b9434b9a5aa1450faa3f9cb14ea0e8c53bb5d2b3c1bfd1ab4fc03e9f33fbfb0" 50 | dependencies = [ 51 | "rustc_version", 52 | ] 53 | 54 | [[package]] 55 | name = "cfg-if" 56 | version = "1.0.0" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 59 | 60 | [[package]] 61 | name = "chf-emulator" 62 | version = "0.1.0" 63 | 64 | [[package]] 65 | name = "cortex-m" 66 | version = "0.6.4" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "88cdafeafba636c00c467ded7f1587210725a1adfab0c24028a7844b87738263" 69 | dependencies = [ 70 | "aligned", 71 | "bare-metal", 72 | "bitfield", 73 | "volatile-register", 74 | ] 75 | 76 | [[package]] 77 | name = "cortex-m-rt" 78 | version = "0.6.13" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "980c9d0233a909f355ed297ef122f257942de5e0a2cb1c39f60684b65bcb90fb" 81 | dependencies = [ 82 | "cortex-m-rt-macros", 83 | "r0", 84 | ] 85 | 86 | [[package]] 87 | name = "cortex-m-rt-macros" 88 | version = "0.1.8" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "4717562afbba06e760d34451919f5c3bf3ac15c7bb897e8b04862a7428378647" 91 | dependencies = [ 92 | "proc-macro2", 93 | "quote", 94 | "syn", 95 | ] 96 | 97 | [[package]] 98 | name = "embedded-hal" 99 | version = "0.2.4" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "fa998ce59ec9765d15216393af37a58961ddcefb14c753b4816ba2191d865fcb" 102 | dependencies = [ 103 | "nb 0.1.3", 104 | "void", 105 | ] 106 | 107 | [[package]] 108 | name = "embedded-sdmmc" 109 | version = "0.3.0" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "6d3bf0a2b5becb87e9a329d9290f131e4d10fec39b56d129926826a7cbea1e7a" 112 | dependencies = [ 113 | "byteorder", 114 | "embedded-hal", 115 | "log", 116 | "nb 0.1.3", 117 | ] 118 | 119 | [[package]] 120 | name = "generic-array" 121 | version = "0.12.3" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" 124 | dependencies = [ 125 | "typenum", 126 | ] 127 | 128 | [[package]] 129 | name = "generic-array" 130 | version = "0.13.2" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "0ed1e761351b56f54eb9dcd0cfaca9fd0daecf93918e1cfc01c8a3d26ee7adcd" 133 | dependencies = [ 134 | "typenum", 135 | ] 136 | 137 | [[package]] 138 | name = "generic-array" 139 | version = "0.14.4" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" 142 | dependencies = [ 143 | "typenum", 144 | "version_check", 145 | ] 146 | 147 | [[package]] 148 | name = "log" 149 | version = "0.4.14" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 152 | dependencies = [ 153 | "cfg-if", 154 | ] 155 | 156 | [[package]] 157 | name = "main" 158 | version = "0.1.0" 159 | dependencies = [ 160 | "chf-emulator", 161 | "cortex-m", 162 | "cortex-m-rt", 163 | "embedded-hal", 164 | "embedded-sdmmc", 165 | "nb 1.0.0", 166 | "panic-halt", 167 | "rtt-target", 168 | "stm32f1xx-hal", 169 | ] 170 | 171 | [[package]] 172 | name = "nb" 173 | version = "0.1.3" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" 176 | dependencies = [ 177 | "nb 1.0.0", 178 | ] 179 | 180 | [[package]] 181 | name = "nb" 182 | version = "1.0.0" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "546c37ac5d9e56f55e73b677106873d9d9f5190605e41a856503623648488cae" 185 | 186 | [[package]] 187 | name = "panic-halt" 188 | version = "0.2.0" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "de96540e0ebde571dc55c73d60ef407c653844e6f9a1e2fdbd40c07b9252d812" 191 | 192 | [[package]] 193 | name = "proc-macro2" 194 | version = "1.0.24" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" 197 | dependencies = [ 198 | "unicode-xid", 199 | ] 200 | 201 | [[package]] 202 | name = "quote" 203 | version = "1.0.8" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df" 206 | dependencies = [ 207 | "proc-macro2", 208 | ] 209 | 210 | [[package]] 211 | name = "r0" 212 | version = "0.2.2" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "e2a38df5b15c8d5c7e8654189744d8e396bddc18ad48041a500ce52d6948941f" 215 | 216 | [[package]] 217 | name = "rtt-target" 218 | version = "0.3.0" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "01a267556daa12832ffc20129af69a6db0a28af91bcf12b4468af21f48818f66" 221 | dependencies = [ 222 | "cortex-m", 223 | "ufmt-write", 224 | ] 225 | 226 | [[package]] 227 | name = "rustc_version" 228 | version = "0.2.3" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" 231 | dependencies = [ 232 | "semver", 233 | ] 234 | 235 | [[package]] 236 | name = "semver" 237 | version = "0.9.0" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" 240 | dependencies = [ 241 | "semver-parser", 242 | ] 243 | 244 | [[package]] 245 | name = "semver-parser" 246 | version = "0.7.0" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 249 | 250 | [[package]] 251 | name = "stable_deref_trait" 252 | version = "1.2.0" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 255 | 256 | [[package]] 257 | name = "stm32f1" 258 | version = "0.11.0" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "849b1e8d9bcfd792c9d9178cf86165d299a661c26e35d9322ae9382d3f3fe460" 261 | dependencies = [ 262 | "bare-metal", 263 | "cortex-m", 264 | "cortex-m-rt", 265 | "vcell", 266 | ] 267 | 268 | [[package]] 269 | name = "stm32f1xx-hal" 270 | version = "0.6.1" 271 | source = "registry+https://github.com/rust-lang/crates.io-index" 272 | checksum = "af9b9e5d7c2901ee39fc9527412327a1fe08f1d84e9d7f4b3497448e655e5098" 273 | dependencies = [ 274 | "as-slice", 275 | "cast", 276 | "cortex-m", 277 | "cortex-m-rt", 278 | "embedded-hal", 279 | "nb 0.1.3", 280 | "stm32f1", 281 | "void", 282 | ] 283 | 284 | [[package]] 285 | name = "syn" 286 | version = "1.0.57" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "4211ce9909eb971f111059df92c45640aad50a619cf55cd76476be803c4c68e6" 289 | dependencies = [ 290 | "proc-macro2", 291 | "quote", 292 | "unicode-xid", 293 | ] 294 | 295 | [[package]] 296 | name = "typenum" 297 | version = "1.12.0" 298 | source = "registry+https://github.com/rust-lang/crates.io-index" 299 | checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" 300 | 301 | [[package]] 302 | name = "ufmt-write" 303 | version = "0.1.0" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | checksum = "e87a2ed6b42ec5e28cc3b94c09982969e9227600b2e3dcbc1db927a84c06bd69" 306 | 307 | [[package]] 308 | name = "unicode-xid" 309 | version = "0.2.1" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" 312 | 313 | [[package]] 314 | name = "vcell" 315 | version = "0.1.3" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" 318 | 319 | [[package]] 320 | name = "version_check" 321 | version = "0.9.2" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" 324 | 325 | [[package]] 326 | name = "void" 327 | version = "1.0.2" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" 330 | 331 | [[package]] 332 | name = "volatile-register" 333 | version = "0.2.0" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | checksum = "0d67cb4616d99b940db1d6bd28844ff97108b498a6ca850e5b6191a532063286" 336 | dependencies = [ 337 | "vcell", 338 | ] 339 | -------------------------------------------------------------------------------- /desktop/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::RefCell, env, fs}; 2 | 3 | use minifb::{Key, Scale, ScaleMode, Window, WindowOptions}; 4 | 5 | use chf_emulator::Cpu; 6 | 7 | const WIDTH: usize = 128 * 2; 8 | const HEIGHT: usize = 64 * 2; 9 | 10 | const ROM_0000: &'static [u8] = include_bytes!("../../chf-emulator/roms/SL31253.bin"); 11 | const ROM_0400: &'static [u8] = include_bytes!("../../chf-emulator/roms/SL31254.bin"); 12 | 13 | fn main() { 14 | let args: Vec = env::args().collect(); 15 | 16 | let mut cartridge = [0u8; 1024 * 8]; 17 | 18 | if args.len() > 1 { 19 | let data = fs::read(&args[1]).unwrap(); 20 | for (i, b) in data.iter().enumerate() { 21 | cartridge[i] = *b; 22 | } 23 | } 24 | 25 | let mut seen_opcodes = [false; 256]; 26 | 27 | let (_stream, stream_handle) = rodio::OutputStream::try_default().unwrap(); 28 | 29 | let mut current_sound = Sound::Silence; 30 | let mut sink = rodio::Sink::try_new(&stream_handle).unwrap(); 31 | 32 | let channel_f = DesktopChannelF { 33 | sound: RefCell::new(Sound::Silence), 34 | 35 | pixels: RefCell::new([0u8; 128 * 64]), 36 | 37 | key_1: RefCell::new(false), 38 | key_2: RefCell::new(false), 39 | key_3: RefCell::new(false), 40 | key_4: RefCell::new(false), 41 | r0: RefCell::new(false), 42 | l0: RefCell::new(false), 43 | u0: RefCell::new(false), 44 | d0: RefCell::new(false), 45 | ccw0: RefCell::new(false), 46 | cw0: RefCell::new(false), 47 | pull0: RefCell::new(false), 48 | push0: RefCell::new(false), 49 | r1: RefCell::new(false), 50 | l1: RefCell::new(false), 51 | u1: RefCell::new(false), 52 | d1: RefCell::new(false), 53 | ccw1: RefCell::new(false), 54 | cw1: RefCell::new(false), 55 | pull1: RefCell::new(false), 56 | push1: RefCell::new(false), 57 | }; 58 | 59 | let cartridge = &cartridge; 60 | let mut cpu = Cpu::new(ROM_0000, ROM_0400, cartridge, &channel_f); 61 | cpu.reset(); 62 | 63 | let mut buffer: Vec = vec![0; WIDTH * HEIGHT]; 64 | 65 | let mut window = Window::new( 66 | "Channel F - ESC to exit", 67 | WIDTH, 68 | HEIGHT, 69 | WindowOptions { 70 | resize: true, 71 | scale: Scale::X2, 72 | scale_mode: ScaleMode::AspectRatioStretch, 73 | ..WindowOptions::default() 74 | }, 75 | ) 76 | .expect("Unable to Open Window"); 77 | 78 | // Limit to max ~60 fps update rate 79 | // window.limit_update_rate(Some(std::time::Duration::from_micros(16600))); 80 | 81 | window.set_background_color(0, 0, 20); 82 | 83 | let mut cntr = 0u32; 84 | let mut exe_cntr = 0u32; 85 | 86 | let mut p_is_down = false; 87 | let mut o_is_down = false; 88 | let mut l_is_down = false; 89 | 90 | let mut show_info = false; 91 | let mut pc_low = u16::MAX; 92 | let mut pc_high = u16::MIN; 93 | 94 | while window.is_open() && !window.is_key_down(Key::Escape) { 95 | *channel_f.key_1.borrow_mut() = window.is_key_down(Key::Key1); 96 | *channel_f.key_2.borrow_mut() = window.is_key_down(Key::Key2); 97 | *channel_f.key_3.borrow_mut() = window.is_key_down(Key::Key3); 98 | *channel_f.key_4.borrow_mut() = window.is_key_down(Key::Key4); 99 | 100 | *channel_f.l0.borrow_mut() = window.is_key_down(Key::A); 101 | *channel_f.r0.borrow_mut() = window.is_key_down(Key::D); 102 | *channel_f.u0.borrow_mut() = window.is_key_down(Key::W); 103 | *channel_f.d0.borrow_mut() = window.is_key_down(Key::S); 104 | *channel_f.ccw0.borrow_mut() = window.is_key_down(Key::Q); 105 | *channel_f.cw0.borrow_mut() = window.is_key_down(Key::E); 106 | *channel_f.pull0.borrow_mut() = window.is_key_down(Key::Y); 107 | *channel_f.push0.borrow_mut() = window.is_key_down(Key::Z); 108 | 109 | *channel_f.l1.borrow_mut() = window.is_key_down(Key::NumPad4); 110 | *channel_f.r1.borrow_mut() = window.is_key_down(Key::NumPad6); 111 | *channel_f.u1.borrow_mut() = window.is_key_down(Key::NumPad8); 112 | *channel_f.d1.borrow_mut() = window.is_key_down(Key::NumPad5); 113 | *channel_f.ccw1.borrow_mut() = window.is_key_down(Key::NumPad7); 114 | *channel_f.cw1.borrow_mut() = window.is_key_down(Key::NumPad9); 115 | *channel_f.pull1.borrow_mut() = window.is_key_down(Key::NumPad1); 116 | *channel_f.push1.borrow_mut() = window.is_key_down(Key::NumPad2); 117 | 118 | if window.is_key_down(Key::L) { 119 | l_is_down = true; 120 | } 121 | 122 | if window.is_key_released(Key::L) && l_is_down { 123 | l_is_down = false; 124 | show_info = !show_info; 125 | } 126 | 127 | if window.is_key_down(Key::O) { 128 | o_is_down = true; 129 | } 130 | 131 | if window.is_key_released(Key::O) && o_is_down { 132 | o_is_down = false; 133 | for i in 0..=255 { 134 | seen_opcodes[i] = false; 135 | } 136 | } 137 | 138 | if window.is_key_down(Key::P) { 139 | p_is_down = true; 140 | } 141 | 142 | if window.is_key_released(Key::P) && p_is_down { 143 | p_is_down = false; 144 | for i in 0..=255 { 145 | println!("{:x} {}", i, seen_opcodes[i]); 146 | } 147 | println!(); 148 | } 149 | 150 | exe_cntr += 1; 151 | if exe_cntr >= 2 { 152 | exe_cntr = 0; 153 | 154 | let pc = cpu.pc0; 155 | 156 | cpu.cycles = 0; 157 | let opcode = cpu.fetch(); 158 | 159 | seen_opcodes[opcode as usize] = true; 160 | 161 | if show_info { 162 | if pc >= pc_high { 163 | pc_high = pc; 164 | } 165 | 166 | if pc <= pc_low { 167 | pc_low = pc; 168 | } 169 | println!("{:x} {:x} .... {:x} - {:x}", pc, opcode, pc_low, pc_high); 170 | } 171 | 172 | cpu.execute(opcode); 173 | } 174 | 175 | if current_sound != *channel_f.sound.borrow() { 176 | current_sound = *channel_f.sound.borrow(); 177 | sink.stop(); 178 | sink = rodio::Sink::try_new(&stream_handle).unwrap(); 179 | 180 | match current_sound { 181 | Sound::Silence => {} 182 | Sound::Frequency1Khz => { 183 | let sound = SineWave::new(1000); 184 | sink.append(sound); 185 | } 186 | Sound::Frequency500Hz => { 187 | let sound = SineWave::new(500); 188 | sink.append(sound); 189 | } 190 | Sound::Frequency120Hz => { 191 | let sound = SineWave::new(120); 192 | sink.append(sound); 193 | } 194 | } 195 | } 196 | 197 | cntr += 1; 198 | if cntr >= 42000 { 199 | cntr = 0; 200 | 201 | let pixels = channel_f.pixels.borrow(); 202 | // column 125 + 126 choose the palette to use 203 | const COLORS: [u32; 16] = [ 204 | 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, // 205 | 0xff7777ff, 0xff0000ff, 0xffff0000, 0xff008800, // 206 | 0xffcccccc, 0xff0000ff, 0xffff0000, 0xff008800, // 207 | 0xff77ff77, 0xff0000ff, 0xffff0000, 0xff008800, // 208 | ]; 209 | 210 | for y in 0..64 { 211 | // The last three columns in the video buffer are special. 212 | // 127 - unknown 213 | // 126 - bit 1 = palette bit 1 214 | // 125 - bit 1 = palette bit 0 (or with 126 bit 0) 215 | // (palette is shifted by two and added to 'color' 216 | // to find palette index which holds the color's index) 217 | let palette = ((pixels[y * 128 + 125] & 2 >> 1) | (pixels[y * 128 + 126])) & 0b11; 218 | 219 | for x in 0..128 { 220 | let pixel = pixels[y * 128 + x]; 221 | 222 | let color = COLORS[(pixel + palette * 4) as usize]; 223 | 224 | let addr = (y * 128 * 2 * 2) + (x * 2); 225 | buffer[addr + 0] = color; 226 | buffer[addr + 1] = color; 227 | buffer[addr + 256] = color; 228 | buffer[addr + 257] = color; 229 | } 230 | } 231 | 232 | // We unwrap here as we want this code to exit if it fails 233 | window.update_with_buffer(&buffer, WIDTH, HEIGHT).unwrap(); 234 | } 235 | } 236 | } 237 | 238 | struct DesktopChannelF { 239 | sound: RefCell, 240 | 241 | pixels: RefCell<[u8; 128 * 64]>, 242 | 243 | key_1: RefCell, 244 | key_2: RefCell, 245 | key_3: RefCell, 246 | key_4: RefCell, 247 | 248 | r0: RefCell, 249 | l0: RefCell, 250 | u0: RefCell, 251 | d0: RefCell, 252 | ccw0: RefCell, 253 | cw0: RefCell, 254 | pull0: RefCell, 255 | push0: RefCell, 256 | 257 | r1: RefCell, 258 | l1: RefCell, 259 | u1: RefCell, 260 | d1: RefCell, 261 | ccw1: RefCell, 262 | cw1: RefCell, 263 | pull1: RefCell, 264 | push1: RefCell, 265 | } 266 | 267 | use chf_emulator::ChannelF; 268 | use chf_emulator::Sound; 269 | use rodio::source::SineWave; 270 | 271 | impl<'a> ChannelF for DesktopChannelF { 272 | fn sound(&self, frequency: Sound) { 273 | *self.sound.borrow_mut() = frequency; 274 | } 275 | 276 | fn set_pixel(&self, x: u8, y: u8, value: u8) { 277 | let x = x; 278 | let y = y; 279 | self.pixels.borrow_mut()[x as usize + y as usize * 128usize] = value; 280 | } 281 | 282 | fn key_pressed(&self, key: chf_emulator::Key) -> bool { 283 | match key { 284 | chf_emulator::Key::Start => *self.key_1.borrow(), 285 | chf_emulator::Key::Hold => *self.key_2.borrow(), 286 | chf_emulator::Key::Mode => *self.key_3.borrow(), 287 | chf_emulator::Key::Time => *self.key_4.borrow(), 288 | 289 | chf_emulator::Key::Right0 => *self.r0.borrow(), 290 | chf_emulator::Key::Left0 => *self.l0.borrow(), 291 | chf_emulator::Key::Forward0 => *self.u0.borrow(), 292 | chf_emulator::Key::Back0 => *self.d0.borrow(), 293 | chf_emulator::Key::CounterClockwise0 => *self.ccw0.borrow(), 294 | chf_emulator::Key::Clockwise0 => *self.cw0.borrow(), 295 | chf_emulator::Key::Pull0 => *self.pull0.borrow(), 296 | chf_emulator::Key::Push0 => *self.push0.borrow(), 297 | 298 | chf_emulator::Key::Right1 => *self.r1.borrow(), 299 | chf_emulator::Key::Left1 => *self.l1.borrow(), 300 | chf_emulator::Key::Forward1 => *self.u1.borrow(), 301 | chf_emulator::Key::Back1 => *self.d1.borrow(), 302 | chf_emulator::Key::CounterClockwise1 => *self.ccw1.borrow(), 303 | chf_emulator::Key::Clockwise1 => *self.cw1.borrow(), 304 | chf_emulator::Key::Pull1 => *self.pull1.borrow(), 305 | chf_emulator::Key::Push1 => *self.push1.borrow(), 306 | 307 | _ => false, 308 | } 309 | } 310 | } 311 | -------------------------------------------------------------------------------- /main/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | #![feature(asm)] 4 | #![feature(llvm_asm)] 5 | #![feature(fmt_internals)] 6 | 7 | use core::cell::RefCell; 8 | 9 | use chf_emulator::{ChannelF, Cpu}; 10 | use embedded_sdmmc::{SdMmcSpi, TimeSource, VolumeIdx}; 11 | use nb::block; 12 | use panic_halt as _; 13 | use rtt_target::{rprintln, rtt_init_print}; 14 | 15 | use cortex_m_rt::entry; 16 | 17 | use stm32f1xx_hal::{ 18 | delay::Delay, 19 | gpio::{ 20 | gpioa::PA9, 21 | gpiob::{PB13, PB14, PB15}, 22 | gpioc::PC13, 23 | Alternate, Floating, Input, Output, PullDown, PushPull, 24 | }, 25 | pac::{self, SPI2}, 26 | prelude::*, 27 | pwm::Channel, 28 | spi::{Spi, Spi2NoRemap}, 29 | timer::{Tim2NoRemap, Timer}, 30 | }; 31 | 32 | use embedded_hal::{ 33 | digital::v2::{InputPin, OutputPin}, 34 | spi::{Mode, Phase, Polarity}, 35 | }; 36 | 37 | #[entry] 38 | fn main() -> ! { 39 | rtt_init_print!(); 40 | 41 | // Get access to the core peripherals from the cortex-m crate 42 | let cp = cortex_m::Peripherals::take().unwrap(); 43 | // Get access to the device specific peripherals from the peripheral access crate 44 | let dp = pac::Peripherals::take().unwrap(); 45 | 46 | // Take ownership over the raw flash and rcc devices and convert them into the corresponding 47 | // HAL structs 48 | let mut flash = dp.FLASH.constrain(); 49 | let mut rcc = dp.RCC.constrain(); 50 | 51 | let clocks = rcc 52 | .cfgr 53 | .use_hse(8.mhz()) 54 | .sysclk(72.mhz()) 55 | .pclk1(36.mhz()) 56 | .pclk2(72.mhz()) 57 | .freeze(&mut flash.acr); 58 | 59 | let mut afio = dp.AFIO.constrain(&mut rcc.apb2); 60 | let mut gpioa = dp.GPIOA.split(&mut rcc.apb2); 61 | let mut gpiob = dp.GPIOB.split(&mut rcc.apb2); 62 | let (pa15, pb3, pb4) = afio.mapr.disable_jtag(gpioa.pa15, gpiob.pb3, gpiob.pb4); 63 | 64 | // for testing use the onboard led 65 | let mut gpioc = dp.GPIOC.split(&mut rcc.apb2); 66 | let mut led = gpioc.pc13.into_push_pull_output(&mut gpioc.crh); 67 | 68 | led.set_high().unwrap_or_default(); // on board LED off 69 | 70 | let button_1 = gpiob.pb9.into_pull_down_input(&mut gpiob.crh); 71 | let button_2 = gpiob.pb8.into_pull_down_input(&mut gpiob.crh); 72 | let button_3 = gpiob.pb7.into_pull_down_input(&mut gpiob.crl); 73 | let button_4 = gpiob.pb6.into_pull_down_input(&mut gpiob.crl); 74 | 75 | let mut select_controller_0 = gpioa.pa1.into_push_pull_output(&mut gpioa.crl); 76 | let mut select_controller_1 = gpioa.pa2.into_push_pull_output(&mut gpioa.crl); 77 | let left = gpioa.pa3.into_pull_down_input(&mut gpioa.crl); 78 | let right = gpioa.pa4.into_pull_down_input(&mut gpioa.crl); 79 | let up = gpioa.pa5.into_pull_down_input(&mut gpioa.crl); 80 | let down = gpioa.pa6.into_pull_down_input(&mut gpioa.crl); 81 | let ccw = gpioa.pa7.into_pull_down_input(&mut gpioa.crl); 82 | let cw = gpiob.pb0.into_pull_down_input(&mut gpiob.crl); 83 | let pull = gpiob.pb1.into_pull_down_input(&mut gpiob.crl); 84 | let push = gpiob.pb10.into_pull_down_input(&mut gpiob.crh); 85 | 86 | let peer_bsy = gpioa.pa9.into_pull_down_input(&mut gpioa.crh); 87 | 88 | let mut delay = Delay::new(cp.SYST, clocks); 89 | 90 | // TIM2 91 | let c1 = gpioa.pa0.into_alternate_push_pull(&mut gpioa.crl); 92 | // If you don't want to use all channels, just leave some out 93 | // let c4 = gpioa.pa3.into_alternate_push_pull(&mut gpioa.crl); 94 | let pins = c1; 95 | 96 | let mut pwm = Timer::tim2(dp.TIM2, &clocks, &mut rcc.apb1).pwm::( 97 | pins, 98 | &mut afio.mapr, 99 | 120.hz(), 100 | ); 101 | 102 | let max = pwm.get_max_duty(); 103 | pwm.set_duty(Channel::C1, max / 2); 104 | pwm.disable(Channel::C1); 105 | 106 | // SPI2: other MCU 107 | let pins = ( 108 | gpiob.pb13.into_alternate_push_pull(&mut gpiob.crh), // SCK 109 | gpiob.pb14.into_floating_input(&mut gpiob.crh), // MISO 110 | gpiob.pb15.into_alternate_push_pull(&mut gpiob.crh), // MOSI 111 | ); 112 | let spi_mode = Mode { 113 | polarity: Polarity::IdleLow, 114 | phase: Phase::CaptureOnFirstTransition, 115 | }; 116 | let mut spi = Spi::spi2(dp.SPI2, pins, spi_mode, 20.mhz(), clocks, &mut rcc.apb1); 117 | 118 | let pins_sd = ( 119 | pb3.into_alternate_push_pull(&mut gpiob.crl), 120 | pb4, 121 | gpiob.pb5.into_alternate_push_pull(&mut gpiob.crl), // MOSI 122 | ); 123 | let spi_mode = Mode { 124 | polarity: Polarity::IdleLow, 125 | phase: Phase::CaptureOnFirstTransition, 126 | }; 127 | let spi_sd = Spi::spi1( 128 | dp.SPI1, 129 | pins_sd, 130 | &mut afio.mapr, 131 | spi_mode, 132 | 300.khz(), 133 | clocks, 134 | &mut rcc.apb2, 135 | ); 136 | 137 | let pa15 = pa15.into_push_pull_output(&mut gpioa.crh); 138 | let sd = SdMmcSpi::new(spi_sd, pa15); 139 | 140 | let time_source = FakeTimeSource {}; 141 | let mut controller = embedded_sdmmc::Controller::new(sd, time_source); 142 | controller.device().init().unwrap(); 143 | 144 | let volume = controller.get_volume(VolumeIdx(0)); 145 | let mut volume = volume.unwrap(); 146 | let dir = controller.open_root_dir(&volume); 147 | let dir = dir.unwrap(); 148 | 149 | let mut files = [0u8; 8 * 128]; // 64 files supported at max 150 | let mut idx = 0; 151 | let mut file_count = 0; 152 | 153 | controller 154 | .iterate_dir(&volume, &dir, |entry| { 155 | struct WriteBuffer { 156 | buffer: [u8; 12], 157 | idx: usize, 158 | } 159 | 160 | impl core::fmt::Write for WriteBuffer { 161 | fn write_str(&mut self, s: &str) -> core::fmt::Result { 162 | for c in s.chars() { 163 | self.buffer[self.idx] = c as u8; 164 | self.idx += 1; 165 | } 166 | Ok(()) 167 | } 168 | } 169 | 170 | let mut write_buffer = WriteBuffer { 171 | buffer: [0u8; 12], 172 | idx: 0usize, 173 | }; 174 | 175 | let fname = entry.name.clone(); 176 | core::fmt::write(&mut write_buffer, format_args!("{}", fname)).unwrap_or_default(); 177 | 178 | if write_buffer.buffer[9] == b'B' { 179 | for i in 0..8 { 180 | files[idx + i] = write_buffer.buffer[i]; 181 | } 182 | 183 | idx += 8; 184 | file_count += 1; 185 | } 186 | }) 187 | .unwrap(); 188 | 189 | let mut pb12 = gpiob.pb12.into_push_pull_output(&mut gpiob.crh); 190 | pb12.set_high().unwrap(); 191 | 192 | let mut x: u8; 193 | let mut y: u8; 194 | let mut c: u8; 195 | 196 | let mut playing_sound = chf_emulator::Sound::Silence; 197 | 198 | let channel_f = StmChannelF { 199 | should_set_pixel: RefCell::from(false), 200 | x: RefCell::from(0), 201 | y: RefCell::from(0), 202 | color: RefCell::from(0), 203 | 204 | current_sound: RefCell::from(chf_emulator::Sound::Silence), 205 | 206 | key_1: RefCell::new(false), 207 | key_2: RefCell::new(false), 208 | key_3: RefCell::new(false), 209 | key_4: RefCell::new(false), 210 | r0: RefCell::new(false), 211 | l0: RefCell::new(false), 212 | u0: RefCell::new(false), 213 | d0: RefCell::new(false), 214 | ccw0: RefCell::new(false), 215 | cw0: RefCell::new(false), 216 | pull0: RefCell::new(false), 217 | push0: RefCell::new(false), 218 | r1: RefCell::new(false), 219 | l1: RefCell::new(false), 220 | u1: RefCell::new(false), 221 | d1: RefCell::new(false), 222 | ccw1: RefCell::new(false), 223 | cw1: RefCell::new(false), 224 | pull1: RefCell::new(false), 225 | push1: RefCell::new(false), 226 | }; 227 | 228 | pb12.set_low().unwrap(); // keep NSS low all the time 229 | 230 | // clear screen 231 | for y in 0..64 { 232 | for x in 0..127 { 233 | set_pixel(x, y, 0, &peer_bsy, &mut spi, &mut delay, &mut led); 234 | delay.delay_us(85u16); 235 | } 236 | } 237 | 238 | select_controller_0.set_high().unwrap_or_default(); 239 | delay.delay_us(300u16); 240 | 241 | let mut fileindex = 0; 242 | let mut shown_fileindex = 999; 243 | let mut left_pressed = false; 244 | let mut right_pressed = false; 245 | let mut push_pressed = false; 246 | loop { 247 | if shown_fileindex != fileindex { 248 | let idx = fileindex * 8; 249 | let fname = core::str::from_utf8(&files[idx..(idx + 8)]).unwrap_or_default(); 250 | draw_str(10, 10, fname, &peer_bsy, &mut spi, &mut delay, &mut led); 251 | shown_fileindex = fileindex; 252 | } 253 | 254 | if left_pressed && left.is_low().unwrap_or_default() && fileindex > 0 { 255 | fileindex -= 1; 256 | } 257 | 258 | if right_pressed && right.is_low().unwrap_or_default() && fileindex < file_count { 259 | fileindex += 1; 260 | } 261 | 262 | if push_pressed && push.is_low().unwrap_or_default() { 263 | break; 264 | } 265 | 266 | left_pressed = left.is_high().unwrap_or_default(); 267 | right_pressed = right.is_high().unwrap_or_default(); 268 | push_pressed = push.is_high().unwrap_or_default(); 269 | 270 | delay.delay_us(300u16); 271 | } 272 | 273 | select_controller_0.set_low().unwrap_or_default(); 274 | 275 | // load the cartridge 276 | let idx = fileindex * 8; 277 | let mut full_fname = [0u8; 12]; 278 | for i in 0..8 { 279 | full_fname[i] = files[idx + i]; 280 | } 281 | full_fname[8] = b'.'; 282 | full_fname[9] = b'B'; 283 | full_fname[10] = b'I'; 284 | full_fname[11] = b'N'; 285 | 286 | let file_to_load = core::str::from_utf8(&full_fname).unwrap_or_default(); 287 | 288 | let mut file = controller 289 | .open_file_in_dir( 290 | &mut volume, 291 | &dir, 292 | file_to_load, 293 | embedded_sdmmc::Mode::ReadOnly, 294 | ) 295 | .unwrap(); 296 | unsafe { 297 | controller 298 | .read(&volume, &mut file, &mut CARTRIDGE) 299 | .unwrap_or_default(); 300 | } 301 | 302 | let catridge = unsafe { CARTRIDGE }; 303 | let mut cpu = Cpu::new(ROM_0000, ROM_0400, &catridge, &channel_f); 304 | cpu.reset(); 305 | 306 | let mut cnt = 0; 307 | loop { 308 | cnt += 1; 309 | if cnt > 10000 { 310 | cnt = 0; 311 | 312 | // checking keys is quite slow - better use complete reads of the GPIO registers 313 | handle_keys( 314 | &mut select_controller_0, 315 | &mut select_controller_1, 316 | &button_1, 317 | &button_2, 318 | &button_3, 319 | &button_4, 320 | &right, 321 | &left, 322 | &up, 323 | &down, 324 | &ccw, 325 | &cw, 326 | &pull, 327 | &push, 328 | &channel_f, 329 | ) 330 | } 331 | 332 | cpu.cycles = 0; 333 | let opcode = cpu.fetch(); 334 | cpu.execute(opcode); 335 | 336 | let should_set_pixel = channel_f.should_set_pixel.replace(false); 337 | 338 | if should_set_pixel { 339 | x = channel_f.x.take(); 340 | y = channel_f.y.take(); 341 | c = channel_f.color.take(); 342 | set_pixel(x, y, c, &peer_bsy, &mut spi, &mut delay, &mut led); 343 | } 344 | 345 | if playing_sound != *(channel_f.current_sound.borrow()) { 346 | playing_sound = *(channel_f.current_sound.borrow()); 347 | 348 | match playing_sound { 349 | chf_emulator::Sound::Silence => { 350 | pwm.disable(Channel::C1); 351 | } 352 | chf_emulator::Sound::Frequency1Khz => { 353 | pwm.set_period(1000.hz()); 354 | pwm.enable(Channel::C1); 355 | } 356 | chf_emulator::Sound::Frequency500Hz => { 357 | pwm.set_period(500.hz()); 358 | pwm.enable(Channel::C1); 359 | } 360 | chf_emulator::Sound::Frequency120Hz => { 361 | pwm.set_period(120.hz()); 362 | pwm.enable(Channel::C1); 363 | } 364 | } 365 | } 366 | } 367 | } 368 | 369 | fn draw_str( 370 | x: u8, 371 | y: u8, 372 | value: &str, 373 | peer_bsy: &PA9>, 374 | spi: &mut Spi< 375 | SPI2, 376 | Spi2NoRemap, 377 | ( 378 | PB13>, 379 | PB14>, 380 | PB15>, 381 | ), 382 | >, 383 | delay: &mut Delay, 384 | led: &mut PC13>, 385 | ) { 386 | let mut xx = x; 387 | for c in value.chars() { 388 | for i in (0..CHARACTERS.len()).step_by(9) { 389 | if CHARACTERS[i] == c as u8 { 390 | let data = &CHARACTERS[i + 1..i + 9]; 391 | draw_character(xx, y, data, peer_bsy, spi, delay, led); 392 | } 393 | } 394 | xx += 8; 395 | } 396 | } 397 | 398 | fn draw_character( 399 | x: u8, 400 | y: u8, 401 | data: &[u8], 402 | peer_bsy: &PA9>, 403 | spi: &mut Spi< 404 | SPI2, 405 | Spi2NoRemap, 406 | ( 407 | PB13>, 408 | PB14>, 409 | PB15>, 410 | ), 411 | >, 412 | delay: &mut Delay, 413 | led: &mut PC13>, 414 | ) { 415 | for yy in 0..8 { 416 | for xx in 0u8..8u8 { 417 | let color = data[yy].overflowing_shr((8 - xx) as u32).0 & 1; 418 | set_pixel(x + xx, y + yy as u8, color, peer_bsy, spi, delay, led); 419 | delay.delay_us(85u16); 420 | } 421 | } 422 | } 423 | 424 | fn set_pixel( 425 | x: u8, 426 | y: u8, 427 | c: u8, 428 | peer_bsy: &PA9>, 429 | spi: &mut Spi< 430 | SPI2, 431 | Spi2NoRemap, 432 | ( 433 | PB13>, 434 | PB14>, 435 | PB15>, 436 | ), 437 | >, 438 | delay: &mut Delay, 439 | led: &mut PC13>, 440 | ) { 441 | let mut was_err = false; 442 | let mut snd_byte = 0u8; 443 | let mut z: u8 = 0; 444 | 445 | // XXXXXXXY YYYYYCCC + 0x00 446 | 447 | // if it's not a pixel in the safe are and not a "select palette" pixel ... don't transmit it 448 | if (x < 4 || x > 101 || y < 4 || y > 62) && x != 125 && x != 126 { 449 | return; 450 | } 451 | 452 | // set pixel stuff ... quite ugly right now 453 | loop { 454 | if z == 0 { 455 | snd_byte = x.overflowing_shl(1).0 | (y.overflowing_shr(5).0 & 1); 456 | } else if z == 1 { 457 | snd_byte = y.overflowing_shl(3).0 | c; 458 | } 459 | 460 | let res = block!(spi.send(snd_byte)); 461 | 462 | if let Err(_) = res { 463 | // can we do anything about it here and now? 464 | } 465 | 466 | // for some reason things get out of sync w/o a delay 467 | // this is because the slave is not able to read and send via SPI 468 | // during a scanline 469 | // by looking at the busy pin we know if the other MCU 470 | // is currently drawing pixels or not 471 | let additional_wait_time = if x == 125 || x == 126 { 472 | 10 // will re-color 473 | } else { 474 | 0 475 | }; 476 | 477 | let wait_time = if peer_bsy.is_high().unwrap_or_default() { 478 | 65u16 479 | } else { 480 | 15u16 481 | } + additional_wait_time; 482 | delay.delay_us(wait_time); 483 | 484 | let res = block!(spi.read()); 485 | match res { 486 | Ok(_v) => { 487 | if _v != 0x7f { 488 | led.toggle().unwrap(); 489 | was_err = true; 490 | } 491 | } 492 | Err(e) => { 493 | rprintln!("send err {:?}", e); 494 | was_err = true; 495 | } 496 | } 497 | 498 | if !was_err { 499 | z += 1; 500 | 501 | if z >= 2 { 502 | break; 503 | } 504 | } 505 | 506 | was_err = false; 507 | } 508 | } 509 | 510 | #[derive(Debug)] 511 | pub struct StmChannelF { 512 | should_set_pixel: RefCell, 513 | x: RefCell, 514 | y: RefCell, 515 | color: RefCell, 516 | 517 | current_sound: RefCell, 518 | 519 | key_1: RefCell, 520 | key_2: RefCell, 521 | key_3: RefCell, 522 | key_4: RefCell, 523 | 524 | r0: RefCell, 525 | l0: RefCell, 526 | u0: RefCell, 527 | d0: RefCell, 528 | ccw0: RefCell, 529 | cw0: RefCell, 530 | pull0: RefCell, 531 | push0: RefCell, 532 | 533 | r1: RefCell, 534 | l1: RefCell, 535 | u1: RefCell, 536 | d1: RefCell, 537 | ccw1: RefCell, 538 | cw1: RefCell, 539 | pull1: RefCell, 540 | push1: RefCell, 541 | } 542 | 543 | impl ChannelF for StmChannelF { 544 | fn sound(&self, frequency: chf_emulator::Sound) { 545 | self.current_sound.replace(frequency); 546 | } 547 | 548 | fn set_pixel(&self, x: u8, y: u8, value: u8) { 549 | *self.should_set_pixel.borrow_mut() = true; 550 | *self.x.borrow_mut() = x; 551 | *self.y.borrow_mut() = y; 552 | *self.color.borrow_mut() = value; 553 | } 554 | 555 | fn key_pressed(&self, key: chf_emulator::Key) -> bool { 556 | match key { 557 | chf_emulator::Key::Start => *self.key_1.borrow(), 558 | chf_emulator::Key::Hold => *self.key_2.borrow(), 559 | chf_emulator::Key::Mode => *self.key_3.borrow(), 560 | chf_emulator::Key::Time => *self.key_4.borrow(), 561 | 562 | chf_emulator::Key::Right0 => *self.r0.borrow(), 563 | chf_emulator::Key::Left0 => *self.l0.borrow(), 564 | chf_emulator::Key::Forward0 => *self.u0.borrow(), 565 | chf_emulator::Key::Back0 => *self.d0.borrow(), 566 | chf_emulator::Key::CounterClockwise0 => *self.ccw0.borrow(), 567 | chf_emulator::Key::Clockwise0 => *self.cw0.borrow(), 568 | chf_emulator::Key::Pull0 => *self.pull0.borrow(), 569 | chf_emulator::Key::Push0 => *self.push0.borrow(), 570 | 571 | chf_emulator::Key::Right1 => *self.r1.borrow(), 572 | chf_emulator::Key::Left1 => *self.l1.borrow(), 573 | chf_emulator::Key::Forward1 => *self.u1.borrow(), 574 | chf_emulator::Key::Back1 => *self.d1.borrow(), 575 | chf_emulator::Key::CounterClockwise1 => *self.ccw1.borrow(), 576 | chf_emulator::Key::Clockwise1 => *self.cw1.borrow(), 577 | chf_emulator::Key::Pull1 => *self.pull1.borrow(), 578 | chf_emulator::Key::Push1 => *self.push1.borrow(), 579 | 580 | _ => false, 581 | } 582 | } 583 | } 584 | 585 | pub fn handle_keys( 586 | select_0: &mut O0, 587 | select_1: &mut O1, 588 | 589 | button_1: &I0, 590 | button_2: &I1, 591 | button_3: &I2, 592 | button_4: &I3, 593 | 594 | right: &I4, 595 | left: &I5, 596 | up: &I6, 597 | down: &I7, 598 | ccw: &I8, 599 | cw: &I9, 600 | pull: &I10, 601 | push: &I11, 602 | 603 | channel_f: &StmChannelF, 604 | ) where 605 | O0: OutputPin, 606 | O1: OutputPin, 607 | I0: InputPin, 608 | I1: InputPin, 609 | I2: InputPin, 610 | I3: InputPin, 611 | I4: InputPin, 612 | I5: InputPin, 613 | I6: InputPin, 614 | I7: InputPin, 615 | I8: InputPin, 616 | I9: InputPin, 617 | I10: InputPin, 618 | I11: InputPin, 619 | { 620 | select_0.set_high().unwrap_or_default(); 621 | select_1.set_low().unwrap_or_default(); 622 | 623 | // fast switching of the select line induces wrong readings with longer wires (interferences?) 624 | // waiting here is not really a good thing but it solves the problem for now 625 | for _ in 0..200 { 626 | unsafe { 627 | asm!("nop"); 628 | } 629 | } 630 | 631 | *channel_f.key_1.borrow_mut() = button_1.is_high().unwrap_or(false); 632 | *channel_f.key_2.borrow_mut() = button_2.is_high().unwrap_or(false); 633 | 634 | *channel_f.l0.borrow_mut() = left.is_high().unwrap_or(false); 635 | *channel_f.r0.borrow_mut() = right.is_high().unwrap_or(false); 636 | *channel_f.u0.borrow_mut() = up.is_high().unwrap_or(false); 637 | *channel_f.d0.borrow_mut() = down.is_high().unwrap_or(false); 638 | *channel_f.ccw0.borrow_mut() = ccw.is_high().unwrap_or(false); 639 | *channel_f.cw0.borrow_mut() = cw.is_high().unwrap_or(false); 640 | *channel_f.push0.borrow_mut() = push.is_high().unwrap_or(false); 641 | *channel_f.pull0.borrow_mut() = pull.is_high().unwrap_or(false); 642 | 643 | select_0.set_low().unwrap_or_default(); 644 | select_1.set_high().unwrap_or_default(); 645 | 646 | for _ in 0..200 { 647 | unsafe { 648 | asm!("nop"); 649 | } 650 | } 651 | 652 | *channel_f.key_3.borrow_mut() = button_3.is_high().unwrap_or(false); 653 | *channel_f.key_4.borrow_mut() = button_4.is_high().unwrap_or(false); 654 | 655 | *channel_f.l1.borrow_mut() = left.is_high().unwrap_or(false); 656 | *channel_f.r1.borrow_mut() = right.is_high().unwrap_or(false); 657 | *channel_f.u1.borrow_mut() = up.is_high().unwrap_or(false); 658 | *channel_f.d1.borrow_mut() = down.is_high().unwrap_or(false); 659 | *channel_f.ccw1.borrow_mut() = ccw.is_high().unwrap_or(false); 660 | *channel_f.cw1.borrow_mut() = cw.is_high().unwrap_or(false); 661 | *channel_f.push1.borrow_mut() = push.is_high().unwrap_or(false); 662 | *channel_f.pull1.borrow_mut() = pull.is_high().unwrap_or(false); 663 | 664 | select_0.set_low().unwrap_or_default(); 665 | select_1.set_low().unwrap_or_default(); 666 | } 667 | 668 | struct FakeTimeSource; 669 | 670 | impl TimeSource for FakeTimeSource { 671 | fn get_timestamp(&self) -> embedded_sdmmc::Timestamp { 672 | embedded_sdmmc::Timestamp { 673 | year_since_1970: 0, 674 | zero_indexed_month: 0, 675 | zero_indexed_day: 0, 676 | hours: 0, 677 | minutes: 0, 678 | seconds: 0, 679 | } 680 | } 681 | } 682 | 683 | const ROM_0000: &'static [u8] = include_bytes!("../../chf-emulator/roms/SL31253.bin"); 684 | const ROM_0400: &'static [u8] = include_bytes!("../../chf-emulator/roms/SL31254.bin"); 685 | 686 | static mut CARTRIDGE: [u8; 4096] = [0u8; 4096]; 687 | 688 | const CHARACTERS: &'static [u8] = &[ 689 | b'A', 0b00011000, 0b00100100, 0b01000010, 0b01000010, 0b01111110, 0b01000010, 0b01000010, 690 | 0b01000010, b'B', 0b11111000, 0b10000100, 0b10000100, 0b11111100, 0b10000010, 0b10000010, 691 | 0b10000010, 0b11111100, b'C', 0b11111110, 0b10000000, 0b10000000, 0b10000000, 0b10000000, 692 | 0b10000000, 0b10000000, 0b11111110, b'D', 0b11111000, 0b10000100, 0b10000010, 0b10000010, 693 | 0b10000010, 0b10000010, 0b10000100, 0b11111000, b'E', 0b11111110, 0b10000000, 0b10000000, 694 | 0b11111110, 0b10000000, 0b10000000, 0b10000000, 0b11111110, b'F', 0b11111110, 0b10000000, 695 | 0b10000000, 0b11111110, 0b10000000, 0b10000000, 0b10000000, 0b10000000, b'G', 0b11111110, 696 | 0b10000000, 0b10000000, 0b10001110, 0b10000010, 0b10000010, 0b10000010, 0b11111110, b'H', 697 | 0b10000010, 0b10000010, 0b10000010, 0b11111110, 0b10000010, 0b10000010, 0b10000010, 0b10000010, 698 | b'I', 0b00111000, 0b00010000, 0b00010000, 0b00010000, 0b00010000, 0b00010000, 0b00010000, 699 | 0b00111000, b'J', 0b00000100, 0b00000100, 0b00000100, 0b00000100, 0b00000100, 0b00000100, 700 | 0b01000100, 0b00111000, b'K', 0b10000010, 0b10000100, 0b10001000, 0b11110000, 0b10001000, 701 | 0b10000100, 0b10000010, 0b10000010, b'L', 0b10000000, 0b10000000, 0b10000000, 0b10000000, 702 | 0b10000000, 0b10000000, 0b10000000, 0b11111110, b'M', 0b10000010, 0b11000110, 0b10011010, 703 | 0b10000010, 0b10000010, 0b10000010, 0b10000010, 0b10000010, b'N', 0b10000010, 0b11000010, 704 | 0b10100010, 0b10010010, 0b10010010, 0b10001010, 0b10000110, 0b10000010, b'O', 0b01111100, 705 | 0b10000010, 0b10000010, 0b10000010, 0b10000010, 0b10000010, 0b10000010, 0b01111100, b'P', 706 | 0b11111110, 0b10000010, 0b10000010, 0b11111100, 0b10000000, 0b10000000, 0b10000000, 0b10000000, 707 | b'Q', 0b01111100, 0b10000010, 0b10000010, 0b10000010, 0b10000010, 0b10001010, 0b10000110, 708 | 0b01111110, b'R', 0b11111110, 0b10000010, 0b10000010, 0b11111100, 0b10000010, 0b10000010, 709 | 0b10000010, 0b10000010, b'S', 0b01111100, 0b10000010, 0b10000000, 0b10000000, 0b01111100, 710 | 0b00000010, 0b10000010, 0b01111000, b'T', 0b11111110, 0b00010000, 0b00010000, 0b00010000, 711 | 0b00010000, 0b00010000, 0b00010000, 0b00010000, b'U', 0b10000010, 0b10000010, 0b10000010, 712 | 0b10000010, 0b10000010, 0b10000010, 0b10000010, 0b01111100, b'V', 0b10000010, 0b10000010, 713 | 0b10000010, 0b01000100, 0b01000100, 0b01000100, 0b00101000, 0b00010000, b'W', 0b10000010, 714 | 0b10000010, 0b10000010, 0b10000010, 0b10010010, 0b10101010, 0b11000110, 0b10000010, b'X', 715 | 0b10000010, 0b10000010, 0b01000100, 0b01000100, 0b00011000, 0b00100100, 0b00100100, 0b10000010, 716 | b'Y', 0b10000010, 0b10000010, 0b01000100, 0b00101000, 0b00010000, 0b00010000, 0b00010000, 717 | 0b00010000, b'Z', 0b11111110, 0b00000100, 0b00000100, 0b00010000, 0b00100000, 0b01000000, 718 | 0b01000000, 0b11111110, b'0', 0b01111100, 0b10000010, 0b10000010, 0b10000010, 0b10000010, 719 | 0b10000010, 0b10000010, 0b01111100, b'1', 0b00010000, 0b00110000, 0b01010000, 0b00010000, 720 | 0b00010000, 0b00010000, 0b00010000, 0b00010000, b'2', 0b11111110, 0b00000010, 0b00000010, 721 | 0b00000010, 0b11111110, 0b10000000, 0b10000000, 0b11111110, b'3', 0b11111100, 0b00000010, 722 | 0b00000010, 0b11111100, 0b00000010, 0b00000010, 0b00000010, 0b11111100, b'4', 0b10000010, 723 | 0b10000010, 0b10000010, 0b11111110, 0b00000010, 0b00000010, 0b00000010, 0b00000010, b'5', 724 | 0b11111110, 0b10000000, 0b10000000, 0b10000000, 0b11111110, 0b00000010, 0b00000010, 0b11111110, 725 | b'6', 0b01111110, 0b10000000, 0b10000000, 0b11111100, 0b10000010, 0b10000010, 0b10000010, 726 | 0b11111100, b'7', 0b11111110, 0b00000010, 0b00000100, 0b00001000, 0b00010000, 0b00010000, 727 | 0b00010000, 0b00010000, b'8', 0b01111100, 0b10000010, 0b10000010, 0b01111100, 0b10000010, 728 | 0b10000010, 0b10000010, 0b01111100, b'9', 0b11111110, 0b10000010, 0b10000010, 0b11111110, 729 | 0b00000010, 0b00000010, 0b00000010, 0b00000010, 0b11111110, 730 | ]; 731 | -------------------------------------------------------------------------------- /chf-emulator/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | use core::{usize}; 4 | 5 | #[derive(Debug, PartialEq, Clone, Copy)] 6 | pub enum Sound { 7 | Silence, 8 | Frequency1Khz, 9 | Frequency500Hz, 10 | Frequency120Hz, 11 | } 12 | 13 | pub enum Key { 14 | Reset, 15 | 16 | Time, 17 | Mode, 18 | Hold, 19 | Start, 20 | 21 | Right0, 22 | Left0, 23 | Back0, 24 | Forward0, 25 | CounterClockwise0, 26 | Clockwise0, 27 | Pull0, 28 | Push0, 29 | 30 | Right1, 31 | Left1, 32 | Back1, 33 | Forward1, 34 | CounterClockwise1, 35 | Clockwise1, 36 | Pull1, 37 | Push1, 38 | } 39 | 40 | pub trait ChannelF { 41 | fn sound(&self, frequency: Sound); 42 | 43 | fn set_pixel(&self, x: u8, y: u8, value: u8); 44 | 45 | fn key_pressed(&self, key: Key) -> bool; 46 | } 47 | 48 | pub struct Cpu<'a> { 49 | pub a: u8, // accumulator 50 | pub scratchpad: [u8; 64], 51 | pub isar: u8, // scratchpad address (ISAR) 52 | pub flags: u8, // (0x1: SF, 0x2: CF, 0x4: ZF, 0x8: OF) 53 | pub icb_flag: u8, // 0x10: ICB 54 | 55 | pub pc0: u16, 56 | pub pc1: u16, 57 | pub dc0: u16, 58 | pub dc1: u16, 59 | 60 | pub cycles: u16, 61 | 62 | memory_0000: &'a [u8], 63 | memory_0400: &'a [u8], 64 | memory_0800: &'a [u8], 65 | ram_2800: [u8; 0x800], // 0x2800 ..= 0x3000 66 | 67 | pub xmemory: [u8; 0x400], 68 | xregister: u16, 69 | 70 | channel_f: &'a dyn ChannelF, 71 | 72 | io_latch: [u8; 256], 73 | 74 | x: u8, 75 | y: u8, 76 | color: u8, 77 | } 78 | 79 | impl<'a> Cpu<'a> { 80 | pub fn new( 81 | rom_0: &'a [u8], 82 | rom_400: &'a [u8], 83 | cartridge: &'a [u8], 84 | channel_f: &'a dyn ChannelF, 85 | ) -> Cpu<'a> { 86 | Cpu { 87 | a: 0, 88 | scratchpad: [0u8; 64], 89 | isar: 0, 90 | flags: 0, 91 | icb_flag: 0, 92 | pc0: 0, 93 | pc1: 0, 94 | dc0: 0, 95 | dc1: 0, 96 | cycles: 0, 97 | memory_0000: rom_0, 98 | memory_0400: rom_400, 99 | memory_0800: cartridge, 100 | ram_2800: [0u8; 0x800], 101 | 102 | xmemory: [1u8; 0x400], 103 | xregister: 0u16, 104 | 105 | channel_f: channel_f, 106 | io_latch: [0u8; 256], 107 | 108 | x: 0, 109 | y: 0, 110 | color: 0, 111 | } 112 | } 113 | 114 | pub fn reset(&mut self) { 115 | self.pc0 = 0; 116 | self.pc1 = 0; 117 | } 118 | 119 | fn result_0czs0o(&mut self, v: u8) -> u8 { 120 | self.flags = 0; 121 | if v < 0x80 { 122 | self.flags += 0x1 // SF 123 | } 124 | 125 | if v == 0 { 126 | self.flags += 0x4 // ZF 127 | } 128 | 129 | v 130 | } 131 | 132 | fn add_czso(&mut self, v1: u8, v2: u8, c: u8) -> u8 { 133 | let full_res = v1 as u16 + v2 as u16 + c as u16; 134 | let res = (full_res & 0xff) as u8; 135 | 136 | self.flags = 0; 137 | 138 | if res < 0x80 { 139 | self.flags |= 0x1; // SF 140 | } 141 | 142 | if res == 0 { 143 | self.flags |= 0x4 // ZF 144 | } 145 | 146 | if (full_res & 0x100) != 0 { 147 | self.flags |= 0x2 // CF 148 | } 149 | 150 | if ((v1 ^ res) & (v2 ^ res) & 0x80) != 0 { 151 | self.flags |= 0x8; // OF 152 | } 153 | 154 | res 155 | } 156 | 157 | fn adddec(&mut self, v1: u8, v2: u8) -> u8 { 158 | /* From F8 Guide To programming description of AMD 159 | * binary add the addend to the binary sum of the augend and $66 160 | * *NOTE* the binary addition of the augend to $66 is done before AMD is called 161 | * record the status of the carry and intermediate carry 162 | * add a factor to the sum based on the carry and intermediate carry: 163 | * - no carry, no intermediate carry, add $AA 164 | * - no carry, intermediate carry, add $A0 165 | * - carry, no intermediate carry, add $0A 166 | * - carry, intermediate carry, add $00 167 | * any carry from the low-order digit is suppressed 168 | * *NOTE* status flags are updated prior to the factor being added 169 | */ 170 | let augend = v1; 171 | let addend = v2; 172 | 173 | let mut tmp = augend.overflowing_add(addend).0; 174 | 175 | let mut c = 0u8; // high order carry 176 | let mut ic = 0u8; // low order carry 177 | 178 | if ((augend as u16 + addend as u16) & 0xff0) > 0xf0 { 179 | c = 1; 180 | } 181 | 182 | if (augend & 0x0f) + (addend & 0x0f) > 0x0F { 183 | ic = 1; 184 | } 185 | 186 | self.flags = 0; 187 | self.add_czso(augend, addend, 0); 188 | 189 | if c == 0 && ic == 0 { 190 | tmp = ((tmp.overflowing_add(0xa0).0) & 0xf0) + ((tmp.overflowing_add(0x0a).0) & 0x0f); 191 | } 192 | 193 | if c == 0 && ic == 1 { 194 | tmp = ((tmp.overflowing_add(0xa0).0) & 0xf0) + (tmp & 0x0f); 195 | } 196 | 197 | if c == 1 && ic == 0 { 198 | tmp = (tmp & 0xf0) + ((tmp.overflowing_add(0x0a).0) & 0x0f); 199 | } 200 | 201 | return tmp; 202 | } 203 | 204 | fn cmp(&mut self, v1: u8, v2: u8) { 205 | self.add_czso(v2, v1 ^ 0xff, 1); 206 | } 207 | 208 | fn branch(&mut self, cond: bool) { 209 | let offset = signed_byte(self.fetch()) - 1; 210 | if cond { 211 | if offset > 0 { 212 | self.pc0 = self.pc0.overflowing_add(offset as u16).0; 213 | //self.pc0 += offset as u16; 214 | } else { 215 | self.pc0 = self.pc0.overflowing_sub((offset * -1) as u16).0; 216 | //self.pc0 -= (offset * -1) as u16; 217 | } 218 | self.cycles += 2; 219 | } 220 | } 221 | 222 | fn inc_isl(&mut self) { 223 | self.isar = ((self.isar.overflowing_add(1).0) & 0x7) + (self.isar & 0x38); 224 | } 225 | 226 | fn dec_isl(&mut self) { 227 | self.isar = ((self.isar.overflowing_sub(1).0) & 0x7) + (self.isar & 0x38); 228 | } 229 | 230 | pub fn fetch(&mut self) -> u8 { 231 | let res = self.get_from_memory(self.pc0); 232 | self.pc0 += 1; 233 | res 234 | } 235 | 236 | fn get_from_memory(&self, addr: u16) -> u8 { 237 | let addr = addr as usize; 238 | match addr { 239 | 0..=0x3ff => self.memory_0000[addr], 240 | 0x400..=0x7ff => self.memory_0400[addr - 0x400], 241 | 0x800..=0x27ff => { 242 | if self.memory_0800.len() > addr - 0x800 { 243 | self.memory_0800[addr - 0x800] 244 | } else { 245 | 0xff 246 | } 247 | } 248 | 0x2800..=0x2fff => self.ram_2800[addr - 0x2800], 249 | _ => 0xff, 250 | } 251 | } 252 | 253 | fn read(&self, addr: u16) -> u8 { 254 | if addr < 0x4000 { 255 | self.get_from_memory(addr) 256 | } else { 257 | 0xff 258 | } 259 | } 260 | 261 | fn write(&mut self, _addr: u16, _v: u8) { 262 | todo!("write"); 263 | } 264 | 265 | fn inport(&mut self, port: u8) -> u8 { 266 | if port == 0 { 267 | let mut res = 0xf; 268 | 269 | if self.channel_f.key_pressed(Key::Start) { 270 | res ^= 0x1; 271 | } 272 | 273 | if self.channel_f.key_pressed(Key::Hold) { 274 | res ^= 0x2; 275 | } 276 | 277 | if self.channel_f.key_pressed(Key::Mode) { 278 | res ^= 0x4; 279 | } 280 | 281 | if self.channel_f.key_pressed(Key::Time) { 282 | res ^= 0x8; 283 | } 284 | 285 | return res; 286 | } 287 | 288 | if port == 1 { 289 | let mut input = 0; 290 | 291 | if self.channel_f.key_pressed(Key::Right0) { 292 | input += 1; 293 | } 294 | 295 | if self.channel_f.key_pressed(Key::Left0) { 296 | input += 2; 297 | } 298 | 299 | if self.channel_f.key_pressed(Key::Back0) { 300 | input += 4; 301 | } 302 | 303 | if self.channel_f.key_pressed(Key::Forward0) { 304 | input += 8; 305 | } 306 | 307 | if self.channel_f.key_pressed(Key::CounterClockwise0) { 308 | input += 16; 309 | } 310 | 311 | if self.channel_f.key_pressed(Key::Clockwise0) { 312 | input += 32; 313 | } 314 | 315 | if self.channel_f.key_pressed(Key::Pull0) { 316 | input += 64; 317 | } 318 | 319 | if self.channel_f.key_pressed(Key::Push0) { 320 | input += 128; 321 | } 322 | 323 | if self.io_latch[0] & 0x40 == 0 { 324 | return input ^ 0xff; 325 | } 326 | } 327 | 328 | if port == 4 { 329 | let mut input = 0; 330 | 331 | if self.channel_f.key_pressed(Key::Right1) { 332 | input += 1; 333 | } 334 | 335 | if self.channel_f.key_pressed(Key::Left1) { 336 | input += 2; 337 | } 338 | 339 | if self.channel_f.key_pressed(Key::Back1) { 340 | input += 4; 341 | } 342 | 343 | if self.channel_f.key_pressed(Key::Forward1) { 344 | input += 8; 345 | } 346 | 347 | if self.channel_f.key_pressed(Key::CounterClockwise1) { 348 | input += 16; 349 | } 350 | 351 | if self.channel_f.key_pressed(Key::Clockwise1) { 352 | input += 32; 353 | } 354 | 355 | if self.channel_f.key_pressed(Key::Pull1) { 356 | input += 64; 357 | } 358 | 359 | if self.channel_f.key_pressed(Key::Push1) { 360 | input += 128; 361 | } 362 | 363 | if self.io_latch[0] & 0x40 == 0 { 364 | return input ^ 0xff; 365 | } 366 | } 367 | 368 | if port == 0x20 || port == 0x24 { 369 | self.xmemory_update(); 370 | return ((self.xregister.overflowing_shr(8).0) & 0xff) as u8 | self.io_latch[port as usize]; 371 | } 372 | 373 | if port == 0x21 || port == 0x25 { 374 | self.xmemory_update(); 375 | return (self.xregister & 0xff) as u8 | self.io_latch[port as usize]; 376 | } 377 | 378 | self.io_latch[port as usize] 379 | } 380 | 381 | fn outport(&mut self, port: u8, v: u8) { 382 | let old = self.io_latch[port as usize]; 383 | self.io_latch[port as usize] = v; 384 | 385 | match port { 386 | 1 => self.color = v.overflowing_shr(6).0 ^ 0x3, 387 | 4 => self.x = (v & 0x7f) ^ 0x7f, 388 | 5 => { 389 | self.y = (v & 0x3f) ^ 0x3f; 390 | 391 | self.channel_f.sound(match v.overflowing_shr(6).0 { 392 | 0 => Sound::Silence, 393 | 1 => Sound::Frequency1Khz, 394 | 2 => Sound::Frequency500Hz, 395 | 3 => Sound::Frequency120Hz, 396 | _ => Sound::Silence, 397 | }); 398 | } 399 | 0 => { 400 | if v & 0x20 == 0 && old & 0x20 != 0 { 401 | self.channel_f.set_pixel(self.x, self.y, self.color); 402 | } 403 | } 404 | 0x20 | 0x24 => { 405 | self.xregister = (self.xregister & 0xff) + ((v as u16 & 0xf).overflowing_shl(8).0); 406 | self.xmemory_update(); 407 | } 408 | 0x21 | 0x25 => { 409 | self.xregister = (self.xregister & 0xff00) + (v as u16); 410 | self.xmemory_update(); 411 | } 412 | _ => (), 413 | } 414 | } 415 | 416 | fn xmemory_update(&mut self) { 417 | let addr = (self.xregister & 0xff) + ((self.xregister.overflowing_shr(1).0) & 0x300); 418 | if self.xregister & 0x100 != 0 { 419 | self.xmemory[addr as usize] = ((self.xregister.overflowing_shr(11).0) & 1) as u8; 420 | } else { 421 | self.xregister = (self.xregister & 0xfff) + (self.xmemory[addr as usize].overflowing_shl(15).0 as u16); 422 | } 423 | } 424 | 425 | pub fn execute(&mut self, opcode: u8) { 426 | match opcode { 427 | // LR 428 | 0x00..=0x03 => { 429 | self.cycles += 4; 430 | self.a = self.scratchpad[12usize + opcode as usize]; 431 | } 432 | 0x04..=0x07 => { 433 | self.cycles += 4; 434 | self.scratchpad[12usize + (opcode - 0x04) as usize] = self.a; 435 | } 436 | 0x08 => { 437 | self.cycles += 16; 438 | self.scratchpad[12] = (self.pc1.overflowing_shr(8).0 & 0xff) as u8; 439 | self.scratchpad[13] = (self.pc1 & 0xff) as u8; 440 | } 441 | 0x09 => { 442 | self.cycles += 16; 443 | self.pc1 = 444 | (self.scratchpad[12] as u16).overflowing_shl(8).0 + self.scratchpad[13] as u16; 445 | } 446 | 0x0a => { 447 | self.cycles += 4; 448 | self.a = self.isar; 449 | } 450 | 0x0b => { 451 | self.cycles += 4; 452 | self.isar = self.a & 0x3f; 453 | } 454 | // PK 455 | 0x0c => { 456 | self.cycles += 16; 457 | self.pc1 = self.pc0; 458 | self.pc0 = (self.scratchpad[12] as u16) 459 | .overflowing_shl(8) 460 | .0 461 | .overflowing_add(self.scratchpad[13] as u16) 462 | .0; 463 | } 464 | // LR 465 | 0x0d => { 466 | self.cycles += 16; 467 | self.pc0 = (self.scratchpad[14] as u16) 468 | .overflowing_shl(8) 469 | .0 470 | .overflowing_add(self.scratchpad[15] as u16) 471 | .0; 472 | } 473 | 0x0e => { 474 | self.cycles += 16; 475 | self.scratchpad[14] = (self.dc0.overflowing_shr(8).0) as u8; 476 | self.scratchpad[15] = (self.dc0 & 0xff) as u8; 477 | } 478 | 0x0f => { 479 | self.cycles += 16; 480 | self.dc0 = (self.scratchpad[14] as u16) 481 | .overflowing_shl(8) 482 | .0 483 | .overflowing_add(self.scratchpad[15] as u16) 484 | .0; 485 | } 486 | 0x10 => { 487 | self.cycles += 16; 488 | self.dc0 = (self.scratchpad[10] as u16) 489 | .overflowing_shl(8) 490 | .0 491 | .overflowing_add(self.scratchpad[11] as u16) 492 | .0; 493 | } 494 | 0x11 => { 495 | self.cycles += 16; 496 | self.scratchpad[10] = self.dc0.overflowing_shr(8).0 as u8; 497 | self.scratchpad[11] = (self.dc0 & 0xff) as u8; 498 | } 499 | // SR 1 500 | 0x12 => { 501 | self.cycles += 4; 502 | self.a = self.result_0czs0o(self.a.overflowing_shr(1).0 & 0xff); 503 | 504 | self.flags = (self.flags | 0b0001) & 0b0101; 505 | } 506 | // SL 1 507 | 0x13 => { 508 | self.cycles += 4; 509 | self.a = self.result_0czs0o(self.a.overflowing_shl(1).0 & 0xff); 510 | self.flags = self.flags & 0b0101; 511 | } 512 | // SR 4 513 | 0x14 => { 514 | self.cycles += 4; 515 | self.a = self.result_0czs0o(self.a.overflowing_shr(4).0 & 0xff); 516 | 517 | self.flags = (self.flags | 0b0001) & 0b0101; 518 | } 519 | // SL 4 520 | 0x15 => { 521 | self.cycles += 4; 522 | self.a = self.result_0czs0o(self.a.overflowing_shl(4).0 & 0xff); 523 | self.flags = self.flags & 0b0101; 524 | } 525 | // LM 526 | 0x16 => { 527 | self.cycles += 10; 528 | self.a = self.read(self.dc0); 529 | self.dc0 = self.dc0.overflowing_add(1).0; 530 | } 531 | // ST 532 | 0x17 => { 533 | self.cycles += 10; 534 | self.write(self.dc0, self.a); 535 | self.dc0 = self.dc0.overflowing_add(1).0; 536 | } 537 | // COM 538 | 0x18 => { 539 | self.cycles += 4; 540 | self.a = self.result_0czs0o(!self.a); 541 | self.flags = self.flags & 0b0101; 542 | } 543 | // LNK 544 | 0x19 => { 545 | self.cycles += 4; 546 | self.a = self.add_czso(self.a, 0, self.flags.overflowing_shr(1).0 & 1); 547 | } 548 | // DI 549 | 0x1a => { 550 | self.cycles += 8; 551 | self.icb_flag = 0; 552 | } 553 | // EI 554 | 0x1b => { 555 | self.cycles += 8; 556 | self.icb_flag = 0x10; 557 | } 558 | // POP 559 | 0x1c => { 560 | self.cycles += 8; 561 | self.pc0 = self.pc1; 562 | } 563 | // LR 564 | 0x1d => { 565 | self.cycles += 8; 566 | self.flags = self.scratchpad[9] & 0xf; 567 | self.icb_flag = self.scratchpad[9] & 0x10; 568 | } 569 | 0x1e => { 570 | self.cycles += 4; 571 | self.scratchpad[9] = self.flags + self.icb_flag; 572 | } 573 | // INC 574 | 0x1f => { 575 | self.cycles += 4; 576 | self.a = self.add_czso(self.a, 0, 1); 577 | } 578 | // LI 579 | 0x20 => { 580 | self.cycles += 10; 581 | self.a = self.fetch(); 582 | } 583 | // NI 584 | 0x21 => { 585 | self.cycles += 10; 586 | let fetched = self.fetch(); 587 | self.a = self.result_0czs0o(self.a & fetched); 588 | } 589 | // OI 590 | 0x22 => { 591 | self.cycles += 10; 592 | let fetched = self.fetch(); 593 | self.a = self.result_0czs0o(self.a | fetched); 594 | } 595 | // XI 596 | 0x23 => { 597 | self.cycles += 10; 598 | let fetched = self.fetch(); 599 | self.a = self.result_0czs0o(self.a ^ fetched); 600 | } 601 | // AI 602 | 0x24 => { 603 | self.cycles += 10; 604 | let fetched = self.fetch(); 605 | self.a = self.add_czso(self.a, fetched, 0); 606 | } 607 | // CI 608 | 0x25 => { 609 | self.cycles += 10; 610 | let fetched = self.fetch(); 611 | self.cmp(self.a, fetched); 612 | } 613 | // IN 614 | 0x26 => { 615 | self.cycles += 16; 616 | let port = self.fetch(); 617 | let in_res = self.inport(port); 618 | self.a = self.result_0czs0o(in_res); 619 | } 620 | // OUT 621 | 0x27 => { 622 | self.cycles += 16; 623 | let fetched = self.fetch(); 624 | self.outport(fetched, self.a); 625 | } 626 | // PI 627 | 0x28 => { 628 | self.cycles += 0x1a; 629 | self.a = self.fetch(); 630 | let tmp = self.fetch(); 631 | self.pc1 = self.pc0; 632 | self.pc0 = (self.a as u16).overflowing_shl(8).0 + tmp as u16; 633 | } 634 | // JMP 635 | 0x29 => { 636 | self.cycles += 16; 637 | self.a = self.fetch(); 638 | let tmp = self.fetch() as u16; 639 | self.pc0 = (self.a as u16).overflowing_shl(8).0 + tmp; 640 | } 641 | // DCI 642 | 0x2a => { 643 | self.cycles += 0x18; 644 | self.dc0 = (self.fetch() as u16).overflowing_shl(8).0; 645 | self.dc0 += self.fetch() as u16; 646 | } 647 | // NOP 648 | 0x2b => { 649 | self.cycles += 4; 650 | } 651 | // XDC 652 | 0x2c => { 653 | self.cycles += 8; 654 | let x = self.dc0; 655 | self.dc0 = self.dc1; 656 | self.dc1 = x; 657 | } 658 | // DS 659 | 0x30..=0x3b => { 660 | self.cycles += 6; 661 | let index = opcode as usize - 0x30usize; 662 | self.scratchpad[index] = self.add_czso(self.scratchpad[index], 0xff, 0); 663 | } 664 | 0x3c => { 665 | self.cycles += 6; 666 | self.scratchpad[self.isar as usize] = 667 | self.add_czso(self.scratchpad[self.isar as usize], 0xff, 0); 668 | } 669 | 0x3d => { 670 | self.cycles += 6; 671 | self.scratchpad[self.isar as usize] = 672 | self.add_czso(self.scratchpad[self.isar as usize], 0xff, 0); 673 | self.inc_isl(); 674 | } 675 | 0x3e => { 676 | self.cycles += 6; 677 | self.scratchpad[self.isar as usize] = 678 | self.add_czso(self.scratchpad[self.isar as usize], 0xff, 0); 679 | self.dec_isl(); 680 | } 681 | // LR 682 | 0x40..=0x4b => { 683 | self.cycles += 4; 684 | let index = opcode as usize - 0x40; 685 | self.a = self.scratchpad[index]; 686 | } 687 | 0x4c => { 688 | self.cycles += 4; 689 | self.a = self.scratchpad[self.isar as usize]; 690 | } 691 | 0x4d => { 692 | self.cycles += 4; 693 | self.a = self.scratchpad[self.isar as usize]; 694 | self.inc_isl(); 695 | } 696 | 0x4e => { 697 | self.cycles += 4; 698 | self.a = self.scratchpad[self.isar as usize]; 699 | self.dec_isl(); 700 | } 701 | 0x50..=0x5b => { 702 | self.cycles += 4; 703 | let index = opcode as usize - 0x50usize; 704 | self.scratchpad[index] = self.a; 705 | } 706 | 0x5c => { 707 | self.cycles += 4; 708 | self.scratchpad[self.isar as usize] = self.a; 709 | } 710 | 0x5d => { 711 | self.cycles += 4; 712 | self.scratchpad[self.isar as usize] = self.a; 713 | self.inc_isl(); 714 | } 715 | 0x5e => { 716 | self.cycles += 4; 717 | self.scratchpad[self.isar as usize] = self.a; 718 | self.dec_isl(); 719 | } 720 | // LISU 721 | 0x60..=0x67 => { 722 | self.cycles += 4; 723 | let index = opcode - 0x60; 724 | self.isar = (self.isar & 0x7) + (index & 0x7).overflowing_shl(3).0; 725 | } 726 | // LISL 727 | 0x68..=0x6f => { 728 | self.cycles += 4; 729 | let index = opcode - 0x68; 730 | self.isar = (self.isar & 0x38) + index; 731 | } 732 | // LIS 733 | 0x70..=0x7f => { 734 | self.cycles += 4; 735 | self.a = opcode - 0x70; 736 | } 737 | // BT 738 | 0x80..=0x87 => { 739 | self.cycles += 0xc; 740 | let index = opcode - 0x80; 741 | self.branch((self.flags & index) != 0); 742 | } 743 | // AM 744 | 0x88 => { 745 | self.cycles += 10; 746 | self.a = self.add_czso(self.a, self.read(self.dc0), 0); 747 | self.dc0 = self.dc0.overflowing_add(1).0; 748 | } 749 | // AMD 750 | 0x89 => { 751 | self.cycles += 10; 752 | self.a = self.adddec(self.a, self.read(self.dc0)); 753 | self.dc0 = self.dc0.overflowing_add(1).0; 754 | } 755 | // NM 756 | 0x8a => { 757 | self.cycles += 10; 758 | self.a = self.result_0czs0o(self.a & self.read(self.dc0)); 759 | self.dc0 = self.dc0.overflowing_add(1).0; 760 | self.flags = self.flags & 0b0101; 761 | } 762 | // OM 763 | 0x8b => { 764 | self.cycles += 10; 765 | self.a = self.result_0czs0o(self.a | self.read(self.dc0)); 766 | self.dc0 = self.dc0.overflowing_add(1).0; 767 | self.flags = self.flags & 0b0101; 768 | } 769 | // XM 770 | 0x8c => { 771 | self.cycles += 10; 772 | self.a = self.result_0czs0o(self.a ^ self.read(self.dc0)); 773 | self.dc0 = self.dc0.overflowing_add(1).0; 774 | self.flags = self.flags & 0b0101; 775 | } 776 | // CM 777 | 0x8d => { 778 | self.cycles += 10; 779 | self.cmp(self.a, self.read(self.dc0)); 780 | self.dc0 = self.dc0.overflowing_add(1).0; 781 | } 782 | // ADC 783 | 0x8e => { 784 | self.cycles += 10; 785 | let signed_a = signed_byte(self.a); 786 | if signed_a > 0 { 787 | self.dc0 += signed_a as u16; 788 | } else { 789 | self.dc0 -= (signed_a * -1) as u16; 790 | } 791 | } 792 | // BR7 793 | 0x8f => { 794 | self.cycles += 8; 795 | self.branch(self.isar & 0x7 != 0x7); 796 | } 797 | // BF 798 | 0x90..=0x9f => { 799 | self.cycles += 0xc; 800 | let index = opcode - 0x90; 801 | self.branch(self.flags & index == 0); 802 | } 803 | // INS 804 | 0xa0..=0xa1 => { 805 | self.cycles += 8; 806 | let index = opcode - 0xa0; 807 | let input = self.inport(index); 808 | self.a = self.result_0czs0o(input); 809 | self.flags = self.flags & 0b0101; 810 | } 811 | 0xa2..=0xaf => { 812 | self.cycles += 0x10; 813 | let index = opcode - 0xa0; 814 | let input = self.inport(index); 815 | self.a = self.result_0czs0o(input); 816 | self.flags = self.flags & 0b0101; 817 | } 818 | // OUTS 819 | 0xb0..=0xb1 => { 820 | self.cycles += 8; 821 | let index = opcode - 0xb0; 822 | self.outport(index, self.a); 823 | } 824 | 0xb2..=0xbf => { 825 | self.cycles += 0x10; 826 | let index = opcode - 0xb0; 827 | self.outport(index, self.a); 828 | } 829 | // AS 830 | 0xc0..=0xcb => { 831 | self.cycles += 4; 832 | let index = (opcode - 0xc0) as usize; 833 | self.a = self.add_czso(self.a, self.scratchpad[index], 0); 834 | } 835 | 0xcc => { 836 | self.cycles += 4; 837 | self.a = self.add_czso(self.a, self.scratchpad[self.isar as usize], 0); 838 | } 839 | 0xcd => { 840 | self.cycles += 4; 841 | self.a = self.add_czso(self.a, self.scratchpad[self.isar as usize], 0); 842 | self.inc_isl(); 843 | } 844 | 0xce => { 845 | self.cycles += 4; 846 | self.a = self.add_czso(self.a, self.scratchpad[self.isar as usize], 0); 847 | self.dec_isl(); 848 | } 849 | // ASD 850 | 0xd0..=0xdb => { 851 | self.cycles += 8; 852 | let index = (opcode - 0xd0) as usize; 853 | self.a = self.adddec(self.a, self.scratchpad[index]); 854 | } 855 | 0xdc => { 856 | self.cycles += 8; 857 | self.a = self.adddec(self.a, self.scratchpad[self.isar as usize]); 858 | } 859 | 0xdd => { 860 | self.cycles += 8; 861 | self.a = self.adddec(self.a, self.scratchpad[self.isar as usize]); 862 | self.inc_isl(); 863 | } 864 | 0xde => { 865 | self.cycles += 8; 866 | self.a = self.adddec(self.a, self.scratchpad[self.isar as usize]); 867 | self.dec_isl(); 868 | } 869 | // XS 870 | 0xe0..=0xeb => { 871 | self.cycles += 4; 872 | let index = (opcode - 0xe0) as usize; 873 | self.a = self.result_0czs0o(self.a ^ self.scratchpad[index]); 874 | self.flags = self.flags & 0b0101; 875 | } 876 | 0xec => { 877 | self.cycles += 4; 878 | self.a = self.result_0czs0o(self.a ^ self.scratchpad[self.isar as usize]); 879 | self.flags = self.flags & 0b0101; 880 | } 881 | 0xed => { 882 | self.cycles += 4; 883 | self.a = self.result_0czs0o(self.a ^ self.scratchpad[self.isar as usize]); 884 | self.inc_isl(); 885 | self.flags = self.flags & 0b0101; 886 | } 887 | 0xee => { 888 | self.cycles += 4; 889 | self.a = self.result_0czs0o(self.a ^ self.scratchpad[self.isar as usize]); 890 | self.dec_isl(); 891 | self.flags = self.flags & 0b0101; 892 | } 893 | // NS 894 | 0xf0..=0xfb => { 895 | self.cycles += 4; 896 | let index = (opcode - 0xf0) as usize; 897 | self.a = self.result_0czs0o(self.a & self.scratchpad[index]); 898 | self.flags = self.flags & 0b0101; 899 | } 900 | 0xfc => { 901 | self.cycles += 4; 902 | self.a = self.result_0czs0o(self.a & self.scratchpad[self.isar as usize]); 903 | self.flags = self.flags & 0b0101; 904 | } 905 | 0xfd => { 906 | self.cycles += 4; 907 | self.a = self.result_0czs0o(self.a & self.scratchpad[self.isar as usize]); 908 | self.inc_isl(); 909 | self.flags = self.flags & 0b0101; 910 | } 911 | 0xfe => { 912 | self.cycles += 4; 913 | self.a = self.result_0czs0o(self.a & self.scratchpad[self.isar as usize]); 914 | self.dec_isl(); 915 | self.flags = self.flags & 0b0101; 916 | } 917 | _ => panic!("Unknown opcode {:x}", opcode), 918 | } 919 | } 920 | } 921 | 922 | fn signed_byte(v: u8) -> i8 { 923 | if v < 0x80 { 924 | v as i8 925 | } else { 926 | ((!v as i8) * -1) - 1 927 | } 928 | } 929 | 930 | #[cfg(test)] 931 | mod tests { 932 | extern crate std; 933 | use core::cell::RefCell; 934 | use std::prelude::v1::*; 935 | 936 | use super::*; 937 | 938 | const ROM_0000: &'static [u8] = include_bytes!("../roms/SL31253.bin"); 939 | const ROM_0400: &'static [u8] = include_bytes!("../roms/SL31254.bin"); 940 | const CARTRIDGE: &'static [u8] = include_bytes!("../roms/demo.bin"); 941 | const CARTRIDGE_TEST: &'static [u8] = include_bytes!("../testfiles/test.bin"); 942 | 943 | struct DummyChannelF { 944 | pixels: RefCell<[u8; 128 * 64]>, 945 | 946 | key_pressed: RefCell, 947 | } 948 | 949 | impl ChannelF for DummyChannelF { 950 | fn sound(&self, _frequency: Sound) {} 951 | 952 | fn set_pixel(&self, x: u8, y: u8, value: u8) { 953 | self.pixels.borrow_mut()[x as usize + y as usize * 128usize] = value; 954 | } 955 | 956 | fn key_pressed(&self, key: Key) -> bool { 957 | match key { 958 | Key::Start => self.key_pressed.borrow().clone(), 959 | _ => false, 960 | } 961 | } 962 | } 963 | 964 | #[test] 965 | fn create_cpu() { 966 | let dummy_channel_f = DummyChannelF { 967 | pixels: RefCell::new([0u8; 128 * 64]), 968 | key_pressed: RefCell::new(false), 969 | }; 970 | let mut cpu = Cpu::new(&[], &[], &[], &dummy_channel_f); 971 | cpu.reset(); 972 | } 973 | 974 | #[test] 975 | fn signed_to_unsigned() { 976 | assert_eq!(0x20, signed_byte(0x20)); 977 | assert_eq!(0x7f, signed_byte(0x7f)); 978 | assert_eq!(-2, signed_byte(0b11111110)); 979 | assert_eq!(-126, signed_byte(0b10000010)); 980 | assert_eq!(-8, signed_byte(0xf8)); 981 | } 982 | 983 | #[test] 984 | fn add() { 985 | let dummy_channel_f = DummyChannelF { 986 | pixels: RefCell::new([0u8; 128 * 64]), 987 | key_pressed: RefCell::new(false), 988 | }; 989 | let mut cpu = Cpu::new(&[], &[], &[], &dummy_channel_f); 990 | cpu.reset(); 991 | 992 | assert_eq!(cpu.add_czso(0x07, 0xff,0), 0x06); 993 | assert_eq!(cpu.add_czso(0x07, 0x01,0), 0x08); 994 | 995 | assert_eq!(cpu.add_czso(0xff, 0,1), 0x00); 996 | assert!( cpu.flags & 0x4 != 0 ); 997 | 998 | assert_eq!(cpu.add_czso(0xfe, 0,1), 0xff); 999 | assert!( cpu.flags & 0x4 == 0 ); 1000 | 1001 | } 1002 | 1003 | #[test] 1004 | fn run_some_opcodes() { 1005 | let dummy_channel_f = DummyChannelF { 1006 | pixels: RefCell::new([0u8; 128 * 64]), 1007 | key_pressed: RefCell::new(false), 1008 | }; 1009 | let mut cpu = Cpu::new(&[], &[], &[], &dummy_channel_f); 1010 | cpu.reset(); 1011 | 1012 | cpu.a = 0x8f; 1013 | cpu.execute(0x50); 1014 | assert_eq!(0x8f, cpu.scratchpad[0]); 1015 | assert_eq!(4, cpu.cycles); 1016 | } 1017 | 1018 | #[test] 1019 | fn startup() { 1020 | let dummy_channel_f = DummyChannelF { 1021 | pixels: RefCell::new([0u8; 128 * 64]), 1022 | key_pressed: RefCell::new(false), 1023 | }; 1024 | let catridge = CARTRIDGE; 1025 | let mut cpu = Cpu::new(ROM_0000, ROM_0400, catridge, &dummy_channel_f); 1026 | cpu.reset(); 1027 | 1028 | for _ in 0..55591320 { 1029 | cpu.cycles = 0; 1030 | let opcode = cpu.fetch(); 1031 | cpu.execute(opcode); 1032 | } 1033 | 1034 | let mut screen = String::new(); 1035 | for y in 0..64 { 1036 | screen.push('['); 1037 | for x in 0..128 { 1038 | let p = dummy_channel_f.pixels.borrow_mut()[(y * 128 + x) as usize] & 0xf; 1039 | 1040 | let x = match p { 1041 | 3 => '█', 1042 | 2 => 'X', 1043 | 1 => '|', 1044 | 0 => ' ', 1045 | _ => '?', 1046 | }; 1047 | screen.push(x); 1048 | } 1049 | screen.push(']'); 1050 | } 1051 | 1052 | assert_eq!( 1053 | "[ █ ]\ 1054 | [ █ ]\ 1055 | [ █ ]\ 1056 | [ █ ]\ 1057 | [ █ ]\ 1058 | [ █ ]\ 1059 | [ █ ]\ 1060 | [ █ ]\ 1061 | [ █ ]\ 1062 | [ █████ █████ █████ █████ █████ █ █ █████ █ █████ █ ]\ 1063 | [ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ]\ 1064 | [ ███ █████ █ █████ █ █████ █ █ █ █ █ ]\ 1065 | [ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ]\ 1066 | [ █ █ █ █████ █ █ █████ █ █ █████ █████ █████ █ ]\ 1067 | [ █ ]\ 1068 | [ █ █ █████ █████ █████ █████ █ ]\ 1069 | [ █ █ █ █ █ █ █ █ █ ]\ 1070 | [ █ █ █ █ █ ███ █ █ █ ]\ 1071 | [ █ █ █ █ █ █ █ █ █ ]\ 1072 | [ █ █████ █████ █████ █████ █ ]\ 1073 | [ █ ]\ 1074 | [ █████ █ █ █████ █████ █████ █████ █████ █████ █ █ █ █ █████ █ █ █████ █ ]\ 1075 | [ █ ██ █ █ █ █ █ █ █ █ █ ██ █ ██ ██ █ ██ █ █ █ ]\ 1076 | [ ███ █ █ █ █ ███ █████ █ █████ █ █ █ █ █ █ █ ███ █ █ █ █ █ ]\ 1077 | [ █ █ ██ █ █ █ █ █ █ █ █ █ ██ █ █ █ █ ██ █ █ ]\ 1078 | [ █████ █ █ █ █████ █ █ █ █ █ █████ █ █ █ █ █████ █ █ █ █ ]\ 1079 | [ █ ]\ 1080 | [ █████ █████ █ █ █████ █████ █████ █████ █ █████ █████ █████ █████ █ ]\ 1081 | [ █ █ ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ]\ 1082 | [ █ ███ █ █ █ █ ███ █████ █████ █ ███ █████ █████ ███ █ ]\ 1083 | [ █ █ █ ██ █ █ █ █ █ █ █ █ █ █ █ █ ]\ 1084 | [ █████ █████ █ █ █ █████ █ █ █ █████ █████ █ █ █████ █████ █ ]\ 1085 | [ █ ]\ 1086 | [ █████ █ █ █████ █ █ █████ █ █ █████ █████ █████ █ █ X █ ]\ 1087 | [ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ XX █ ]\ 1088 | [ █████ █ █ █████ █████ ████ █ █ █ █ █ █ █ █ █ X █ ]\ 1089 | [ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ X █ ]\ 1090 | [ █ █████ █████ █ █ █████ █████ █ █ █████ █ █ XXX █ ]\ 1091 | [ █ ]\ 1092 | [ █ ]\ 1093 | [ █ ]\ 1094 | [ █ ]\ 1095 | [ █ ]\ 1096 | [ █ ]\ 1097 | [ █ ]\ 1098 | [ █ ]\ 1099 | [ █ ]\ 1100 | [ █ ]\ 1101 | [ █ ]\ 1102 | [ █ ]\ 1103 | [ █ ]\ 1104 | [ █ ]\ 1105 | [ █ ]\ 1106 | [ █ ]\ 1107 | [ █ ]\ 1108 | [ █ ]\ 1109 | [ █ ]\ 1110 | [ █ ]\ 1111 | [ █ ]\ 1112 | [ █ ]\ 1113 | [ █ ]\ 1114 | [ █ ]\ 1115 | [ █ ]\ 1116 | [ █ ]", 1117 | screen 1118 | ); 1119 | } 1120 | 1121 | #[test] 1122 | fn startup_no_cartridge() { 1123 | let dummy_channel_f = DummyChannelF { 1124 | pixels: RefCell::new([0u8; 128 * 64]), 1125 | key_pressed: RefCell::new(false), 1126 | }; 1127 | let mut cpu = Cpu::new(ROM_0000, ROM_0400, &[], &dummy_channel_f); 1128 | cpu.reset(); 1129 | 1130 | for _ in 0..55591320 { 1131 | cpu.cycles = 0; 1132 | let opcode = cpu.fetch(); 1133 | cpu.execute(opcode); 1134 | } 1135 | 1136 | assert_eq!(195, cpu.pc0); 1137 | } 1138 | 1139 | #[test] 1140 | fn test_generated() { 1141 | let mut pcs: Vec = Vec::new(); 1142 | let f = std::fs::File::open("./testfiles/test.log").unwrap(); 1143 | let file = std::io::BufReader::new(&f); 1144 | for line in std::io::BufRead::lines(file) { 1145 | let line = line.unwrap(); 1146 | pcs.push( line ); 1147 | } 1148 | 1149 | 1150 | let dummy_channel_f = DummyChannelF { 1151 | pixels: RefCell::new([0u8; 128 * 64]), 1152 | key_pressed: RefCell::new(false), 1153 | }; 1154 | let catridge = CARTRIDGE_TEST; 1155 | let mut cpu = Cpu::new(ROM_0000, ROM_0400, catridge, &dummy_channel_f); 1156 | cpu.reset(); 1157 | 1158 | 1159 | let mut pcs_idx = 0usize; 1160 | let mut checking = false; 1161 | for _ in 0..55591320 { 1162 | 1163 | if cpu.pc0 == 0x803 && !checking { 1164 | checking = true; 1165 | } 1166 | 1167 | if cpu.pc0 == 0x1000 { 1168 | break; 1169 | } 1170 | 1171 | if checking { 1172 | let current = std::format!( 1173 | "A={:02X} W={:02X} IS={:02X} R0={:02X} R1={:02X} R2={:02X} R3={:02X} R4={:02X} {:04X}: ", 1174 | cpu.a, 1175 | cpu.flags | cpu.icb_flag, 1176 | cpu.isar, 1177 | cpu.scratchpad[0], 1178 | cpu.scratchpad[1], 1179 | cpu.scratchpad[2], 1180 | cpu.scratchpad[3], 1181 | cpu.scratchpad[4], 1182 | cpu.pc0, 1183 | ); 1184 | 1185 | 1186 | if !pcs[pcs_idx].starts_with(¤t) { 1187 | std::println!("should be \n{} but is \n{}, index={}", &pcs[pcs_idx], ¤t, pcs_idx); 1188 | assert!(false); 1189 | } 1190 | pcs_idx += 1; 1191 | } 1192 | 1193 | cpu.cycles = 0; 1194 | let opcode = cpu.fetch(); 1195 | cpu.execute(opcode); 1196 | } 1197 | 1198 | } 1199 | 1200 | // memory 1201 | // 0x0000 BIOS (BIOS SL31253 or SL90025 to location 0x0, BIOS SL31254 to location 0x400) 1202 | // 0x0800 Cartridge ID (0x55 0x08) 1203 | // 0x0802 Cartrige Start Address 1204 | // 0x2800 additional RAM on cartridge 1205 | } 1206 | -------------------------------------------------------------------------------- /video/src/video/mod.rs: -------------------------------------------------------------------------------- 1 | use cortex_m::Peripherals; 2 | use stm32f1xx_hal::pac::{interrupt, Interrupt}; 3 | use stm32f1xx_hal::{ 4 | gpio::{gpioa::PA0, Output, PushPull}, 5 | pac, 6 | }; 7 | 8 | use core::mem::MaybeUninit; 9 | 10 | static mut TIMER_TIM4: MaybeUninit = MaybeUninit::uninit(); 11 | static mut TIMER_TIM1: MaybeUninit = MaybeUninit::uninit(); 12 | static mut BUSY_PIN: MaybeUninit>> = MaybeUninit::uninit(); 13 | 14 | pub static mut VID_RAM: [u8; 128 * 64] = [0u8; 128 * 64]; 15 | 16 | const ISR_OVERHEAD_CORRECTION: u16 = 110; 17 | 18 | pub fn init_video( 19 | cp: &mut Peripherals, 20 | tim4: pac::TIM4, 21 | tim1: pac::TIM1, 22 | busy: PA0>, 23 | ) { 24 | unsafe { 25 | pac::NVIC::unmask(Interrupt::TIM4); 26 | pac::NVIC::unmask(Interrupt::TIM1_UP); 27 | 28 | cp.NVIC.set_priority(Interrupt::TIM4, 16); 29 | cp.NVIC.set_priority(Interrupt::TIM1_UP, 32); 30 | } 31 | 32 | unsafe { 33 | (*pac::RCC::ptr()) 34 | .apb1enr 35 | .modify(|_, w| w.tim4en().set_bit()); 36 | 37 | (*pac::RCC::ptr()) 38 | .apb2enr 39 | .modify(|_, w| w.tim1en().set_bit()); 40 | } 41 | 42 | // configure TIM4 43 | configure_tim4(&tim4); 44 | 45 | // configure TIM1 46 | configure_tim1(&tim1); 47 | 48 | // make peripherals accessible from the isr 49 | unsafe { 50 | let timer_static = TIMER_TIM4.as_mut_ptr(); 51 | *timer_static = tim4; 52 | 53 | let timer_static = TIMER_TIM1.as_mut_ptr(); 54 | *timer_static = tim1; 55 | 56 | *(BUSY_PIN.as_mut_ptr()) = busy; 57 | } 58 | } 59 | 60 | pub fn start_video() { 61 | schedule(HALF_SCANLINE_ARR, SHORT_SYNC_CRR); 62 | } 63 | 64 | #[inline(always)] 65 | fn schedule(new_arr: u16, new_crr: u16) { 66 | unsafe { 67 | let tim1 = TIMER_TIM1.as_mut_ptr(); 68 | (*tim1).cnt.write(|w| w.bits(0)); 69 | (*tim1).arr.modify(|_, w| w.arr().bits(new_arr - 15)); 70 | // start timer 71 | (*tim1).cr1.modify(|_, w| { 72 | w.cen().set_bit() // START! 73 | }); 74 | 75 | let tim = TIMER_TIM4.as_mut_ptr(); 76 | (*tim).arr.modify(|_, w| w.arr().bits(new_arr)); 77 | (*tim).ccr4.write(|w| w.bits(new_crr as u32)); 78 | // start anew 79 | (*tim).cr1.modify(|_, w| w.cen().set_bit()); 80 | } 81 | } 82 | 83 | fn configure_tim4(tim: &stm32f1xx_hal::pac::TIM4) { 84 | tim.arr.modify(|_, w| { 85 | w.arr().bits(0) // right value in schedule 86 | }); 87 | 88 | tim.ccr4.modify(|_, w| { 89 | w.ccr().bits(0) // right value in schedule 90 | }); 91 | 92 | tim.psc.modify(|_, w| { 93 | w.psc().bits(0) // no prescaler 94 | }); 95 | 96 | // pwm mode etc 97 | tim.ccmr2_output_mut().modify(|_, w| { 98 | w.oc4m() 99 | .pwm_mode2() // pwm mode low/high 100 | .oc4pe() 101 | .clear_bit() // disable output compare preload 102 | .oc4fe() 103 | .set_bit() // enable fast mode 104 | .cc4s() 105 | .output() 106 | }); 107 | 108 | // output enable channel 4 109 | tim.ccer.modify(|_, w| w.cc4e().set_bit()); 110 | 111 | // enable update interrupt 112 | tim.dier.modify(|_, w| w.uie().set_bit()); 113 | 114 | // The psc register is buffered, so we trigger an update event to update it 115 | // Sets the URS bit to prevent an interrupt from being triggered by the UG bit 116 | tim.cr1.modify(|_, w| w.urs().set_bit()); 117 | tim.egr.write(|w| w.ug().set_bit()); 118 | tim.cr1.modify(|_, w| w.urs().clear_bit()); 119 | 120 | tim.cr1.modify(|_, w| { 121 | w.cms() 122 | .bits(0b00) // center aligned etc. 123 | .dir() 124 | .clear_bit() // upcounting 125 | .opm() 126 | .set_bit() // one shot / one pulse 127 | }); 128 | } 129 | 130 | fn configure_tim1(tim: &stm32f1xx_hal::pac::TIM1) { 131 | tim.cnt.write(|w| unsafe { w.bits(0) }); 132 | 133 | tim.arr.modify(|_, w| w.arr().bits(0)); // right value in schedule 134 | 135 | tim.psc.modify(|_, w| { 136 | w.psc().bits(0) // no prescaler 137 | }); 138 | 139 | // enable update interrupt 140 | tim.dier.modify(|_, w| w.uie().set_bit()); 141 | 142 | // The psc register is buffered, so we trigger an update event to update it 143 | // Sets the URS bit to prevent an interrupt from being triggered by the UG bit 144 | tim.cr1.modify(|_, w| w.urs().set_bit()); 145 | tim.egr.write(|w| w.ug().set_bit()); 146 | tim.cr1.modify(|_, w| w.urs().clear_bit()); 147 | 148 | // start timer 149 | tim.cr1.modify(|_, w| { 150 | w.cms() 151 | .bits(0b00) // center aligned etc. 152 | .dir() 153 | .clear_bit() // upcounting 154 | .opm() // one shot / one pulse 155 | .enabled() 156 | }); 157 | } 158 | 159 | static mut IDX: usize = 0usize; 160 | 161 | const START_AT_SCANLINE: usize = 80; 162 | const STOP_AT_SCANLINE: usize = START_AT_SCANLINE + 64 * 3; 163 | 164 | #[interrupt] 165 | fn TIM4() { 166 | unsafe { 167 | let tim = TIMER_TIM4.as_mut_ptr(); 168 | // clear timer interrupt 169 | (*tim).sr.modify(|_, w| w.uif().clear_bit()); 170 | 171 | let has_pixels = DATA[IDX].has_pixels; 172 | let new_arr = DATA[IDX].arr; 173 | let new_crr = DATA[IDX].ccr; 174 | schedule(new_arr, new_crr); 175 | 176 | if has_pixels && IDX >= START_AT_SCANLINE && IDX < STOP_AT_SCANLINE { 177 | let mul = (IDX - START_AT_SCANLINE) / 3 * 128; 178 | draw_pxls( 179 | (VID_RAM.as_mut_ptr() as *const _ as u32) 180 | .overflowing_add(mul as u32) 181 | .0, 182 | ); 183 | } 184 | 185 | IDX += 1; 186 | if IDX >= DATA.len() { 187 | IDX = 0; 188 | } 189 | 190 | let gpioa_set = 0x40010810; 191 | if IDX >= START_AT_SCANLINE && IDX < STOP_AT_SCANLINE { 192 | *(gpioa_set as *mut u32) = 1; 193 | } else { 194 | *(gpioa_set as *mut u32) = 1 << 16; 195 | } 196 | }; 197 | } 198 | 199 | #[inline(always)] 200 | fn draw_pxls(line_pixel_ptr: u32) { 201 | unsafe { 202 | asm!( 203 | "tim4_busy_loop:", 204 | "ldrh {cntr},[{tim4_cnt}]", 205 | "cmp {cntr}, {cntr_dst}", 206 | "blo tim4_busy_loop", 207 | 208 | "mov {gpio_write_data},#0", 209 | "loop:", 210 | 211 | "ldrb {gpio_write_data},[{xpxl_data_ptr}]", 212 | "lsl {gpio_write_data},#3", 213 | "str {gpio_write_data}, [{gpio_reg}]", 214 | "nop", 215 | "nop", 216 | "nop", 217 | "nop", 218 | "nop", 219 | "nop", 220 | "nop", 221 | "nop", 222 | "nop", 223 | "nop", 224 | "nop", 225 | "nop", 226 | "nop", 227 | "add {xpxl_data_ptr}, #1", 228 | 229 | "subs {pxl_count}, #1", 230 | "bne loop", 231 | 232 | "mov {gpio_write_data},#0x0", 233 | "str {gpio_write_data}, [{gpio_reg}]", 234 | 235 | pxl_count = in(reg) 104, // should be approx 128, probably just 104 236 | xpxl_data_ptr = in(reg) line_pixel_ptr, 237 | gpio_write_data = out(reg) _, 238 | gpio_reg = in(reg) 0x40010c0c, 239 | cntr = in(reg) 0, 240 | cntr_dst = in(reg) 1270, // START AT THIS TIM4 CNT VALUE 241 | tim4_cnt = in(reg) 0x4000_0824 // TIM4 CNT 242 | ); 243 | } 244 | } 245 | 246 | #[interrupt] 247 | fn TIM1_UP() { 248 | unsafe { 249 | let tim1 = TIMER_TIM1.as_mut_ptr(); 250 | (*tim1).sr.modify(|_, w| w.uif().clear_bit()); 251 | asm!("wfi"); 252 | } 253 | } 254 | 255 | struct Data { 256 | arr: u16, 257 | ccr: u16, 258 | has_pixels: bool, 259 | } 260 | 261 | const FULL_SCANLINE_ARR: u16 = 2307 * 2 - ISR_OVERHEAD_CORRECTION; // 64 262 | const HALF_SCANLINE_ARR: u16 = 2305 - ISR_OVERHEAD_CORRECTION; 263 | 264 | const BROAD_SYNC_CRR: u16 = 1966 - ISR_OVERHEAD_CORRECTION; // 4.7 265 | const SHORT_SYNC_CRR: u16 = 182 - ISR_OVERHEAD_CORRECTION; // 2.35 266 | const H_SYNC_CRR: u16 = 344 - ISR_OVERHEAD_CORRECTION; // 4.7 267 | // front porch (after pixel data) 1.64 268 | // back purch (before pixel data, after h-sync) 5.7 269 | 270 | const _DATA: [Data; 4] = [ 271 | Data { 272 | arr: HALF_SCANLINE_ARR, 273 | ccr: SHORT_SYNC_CRR, 274 | has_pixels: false, 275 | }, 276 | Data { 277 | arr: HALF_SCANLINE_ARR, 278 | ccr: SHORT_SYNC_CRR, 279 | has_pixels: false, 280 | }, 281 | // scanline 6 - 23 282 | Data { 283 | arr: FULL_SCANLINE_ARR, 284 | ccr: H_SYNC_CRR, 285 | has_pixels: false, 286 | }, 287 | Data { 288 | arr: FULL_SCANLINE_ARR, 289 | ccr: H_SYNC_CRR, 290 | has_pixels: false, 291 | }, 292 | ]; 293 | 294 | const DATA: [Data; 312 + 5 + 3] = [ 295 | // scanline 1 - 5 296 | Data { 297 | arr: HALF_SCANLINE_ARR, 298 | ccr: BROAD_SYNC_CRR, 299 | has_pixels: false, 300 | }, 301 | Data { 302 | arr: HALF_SCANLINE_ARR, 303 | ccr: BROAD_SYNC_CRR, 304 | has_pixels: false, 305 | }, 306 | Data { 307 | arr: HALF_SCANLINE_ARR, 308 | ccr: BROAD_SYNC_CRR, 309 | has_pixels: false, 310 | }, 311 | Data { 312 | arr: HALF_SCANLINE_ARR, 313 | ccr: BROAD_SYNC_CRR, 314 | has_pixels: false, 315 | }, 316 | Data { 317 | arr: HALF_SCANLINE_ARR, 318 | ccr: BROAD_SYNC_CRR, 319 | has_pixels: false, 320 | }, 321 | Data { 322 | arr: HALF_SCANLINE_ARR, 323 | ccr: SHORT_SYNC_CRR, 324 | has_pixels: false, 325 | }, 326 | Data { 327 | arr: HALF_SCANLINE_ARR, 328 | ccr: SHORT_SYNC_CRR, 329 | has_pixels: false, 330 | }, 331 | Data { 332 | arr: HALF_SCANLINE_ARR, 333 | ccr: SHORT_SYNC_CRR, 334 | has_pixels: false, 335 | }, 336 | Data { 337 | arr: HALF_SCANLINE_ARR, 338 | ccr: SHORT_SYNC_CRR, 339 | has_pixels: false, 340 | }, 341 | Data { 342 | arr: HALF_SCANLINE_ARR, 343 | ccr: SHORT_SYNC_CRR, 344 | has_pixels: false, 345 | }, 346 | // scanline 6 - 23 347 | Data { 348 | arr: FULL_SCANLINE_ARR, 349 | ccr: H_SYNC_CRR, 350 | has_pixels: false, 351 | }, 352 | Data { 353 | arr: FULL_SCANLINE_ARR, 354 | ccr: H_SYNC_CRR, 355 | has_pixels: false, 356 | }, 357 | Data { 358 | arr: FULL_SCANLINE_ARR, 359 | ccr: H_SYNC_CRR, 360 | has_pixels: false, 361 | }, 362 | Data { 363 | arr: FULL_SCANLINE_ARR, 364 | ccr: H_SYNC_CRR, 365 | has_pixels: false, 366 | }, 367 | Data { 368 | arr: FULL_SCANLINE_ARR, 369 | ccr: H_SYNC_CRR, 370 | has_pixels: false, 371 | }, 372 | Data { 373 | arr: FULL_SCANLINE_ARR, 374 | ccr: H_SYNC_CRR, 375 | has_pixels: false, 376 | }, 377 | Data { 378 | arr: FULL_SCANLINE_ARR, 379 | ccr: H_SYNC_CRR, 380 | has_pixels: false, 381 | }, 382 | Data { 383 | arr: FULL_SCANLINE_ARR, 384 | ccr: H_SYNC_CRR, 385 | has_pixels: false, 386 | }, 387 | Data { 388 | arr: FULL_SCANLINE_ARR, 389 | ccr: H_SYNC_CRR, 390 | has_pixels: false, 391 | }, 392 | Data { 393 | arr: FULL_SCANLINE_ARR, 394 | ccr: H_SYNC_CRR, 395 | has_pixels: false, 396 | }, 397 | Data { 398 | arr: FULL_SCANLINE_ARR, 399 | ccr: H_SYNC_CRR, 400 | has_pixels: false, 401 | }, 402 | Data { 403 | arr: FULL_SCANLINE_ARR, 404 | ccr: H_SYNC_CRR, 405 | has_pixels: false, 406 | }, 407 | Data { 408 | arr: FULL_SCANLINE_ARR, 409 | ccr: H_SYNC_CRR, 410 | has_pixels: false, 411 | }, 412 | Data { 413 | arr: FULL_SCANLINE_ARR, 414 | ccr: H_SYNC_CRR, 415 | has_pixels: false, 416 | }, 417 | Data { 418 | arr: FULL_SCANLINE_ARR, 419 | ccr: H_SYNC_CRR, 420 | has_pixels: false, 421 | }, 422 | Data { 423 | arr: FULL_SCANLINE_ARR, 424 | ccr: H_SYNC_CRR, 425 | has_pixels: false, 426 | }, 427 | Data { 428 | arr: FULL_SCANLINE_ARR, 429 | ccr: H_SYNC_CRR, 430 | has_pixels: false, 431 | }, 432 | Data { 433 | arr: FULL_SCANLINE_ARR, 434 | ccr: H_SYNC_CRR, 435 | has_pixels: false, 436 | }, 437 | // scanline 24 - 309 438 | Data { 439 | arr: FULL_SCANLINE_ARR, 440 | ccr: H_SYNC_CRR, 441 | has_pixels: true, 442 | }, 443 | Data { 444 | arr: FULL_SCANLINE_ARR, 445 | ccr: H_SYNC_CRR, 446 | has_pixels: true, 447 | }, 448 | Data { 449 | arr: FULL_SCANLINE_ARR, 450 | ccr: H_SYNC_CRR, 451 | has_pixels: true, 452 | }, 453 | Data { 454 | arr: FULL_SCANLINE_ARR, 455 | ccr: H_SYNC_CRR, 456 | has_pixels: true, 457 | }, 458 | Data { 459 | arr: FULL_SCANLINE_ARR, 460 | ccr: H_SYNC_CRR, 461 | has_pixels: true, 462 | }, 463 | Data { 464 | arr: FULL_SCANLINE_ARR, 465 | ccr: H_SYNC_CRR, 466 | has_pixels: true, 467 | }, 468 | Data { 469 | arr: FULL_SCANLINE_ARR, 470 | ccr: H_SYNC_CRR, 471 | has_pixels: true, 472 | }, 473 | Data { 474 | arr: FULL_SCANLINE_ARR, 475 | ccr: H_SYNC_CRR, 476 | has_pixels: true, 477 | }, 478 | Data { 479 | arr: FULL_SCANLINE_ARR, 480 | ccr: H_SYNC_CRR, 481 | has_pixels: true, 482 | }, 483 | Data { 484 | arr: FULL_SCANLINE_ARR, 485 | ccr: H_SYNC_CRR, 486 | has_pixels: true, 487 | }, 488 | Data { 489 | arr: FULL_SCANLINE_ARR, 490 | ccr: H_SYNC_CRR, 491 | has_pixels: true, 492 | }, 493 | Data { 494 | arr: FULL_SCANLINE_ARR, 495 | ccr: H_SYNC_CRR, 496 | has_pixels: true, 497 | }, 498 | Data { 499 | arr: FULL_SCANLINE_ARR, 500 | ccr: H_SYNC_CRR, 501 | has_pixels: true, 502 | }, 503 | Data { 504 | arr: FULL_SCANLINE_ARR, 505 | ccr: H_SYNC_CRR, 506 | has_pixels: true, 507 | }, 508 | Data { 509 | arr: FULL_SCANLINE_ARR, 510 | ccr: H_SYNC_CRR, 511 | has_pixels: true, 512 | }, 513 | Data { 514 | arr: FULL_SCANLINE_ARR, 515 | ccr: H_SYNC_CRR, 516 | has_pixels: true, 517 | }, 518 | Data { 519 | arr: FULL_SCANLINE_ARR, 520 | ccr: H_SYNC_CRR, 521 | has_pixels: true, 522 | }, 523 | Data { 524 | arr: FULL_SCANLINE_ARR, 525 | ccr: H_SYNC_CRR, 526 | has_pixels: true, 527 | }, 528 | Data { 529 | arr: FULL_SCANLINE_ARR, 530 | ccr: H_SYNC_CRR, 531 | has_pixels: true, 532 | }, 533 | Data { 534 | arr: FULL_SCANLINE_ARR, 535 | ccr: H_SYNC_CRR, 536 | has_pixels: true, 537 | }, 538 | Data { 539 | arr: FULL_SCANLINE_ARR, 540 | ccr: H_SYNC_CRR, 541 | has_pixels: true, 542 | }, 543 | Data { 544 | arr: FULL_SCANLINE_ARR, 545 | ccr: H_SYNC_CRR, 546 | has_pixels: true, 547 | }, 548 | Data { 549 | arr: FULL_SCANLINE_ARR, 550 | ccr: H_SYNC_CRR, 551 | has_pixels: true, 552 | }, 553 | Data { 554 | arr: FULL_SCANLINE_ARR, 555 | ccr: H_SYNC_CRR, 556 | has_pixels: true, 557 | }, 558 | Data { 559 | arr: FULL_SCANLINE_ARR, 560 | ccr: H_SYNC_CRR, 561 | has_pixels: true, 562 | }, 563 | Data { 564 | arr: FULL_SCANLINE_ARR, 565 | ccr: H_SYNC_CRR, 566 | has_pixels: true, 567 | }, 568 | Data { 569 | arr: FULL_SCANLINE_ARR, 570 | ccr: H_SYNC_CRR, 571 | has_pixels: true, 572 | }, 573 | Data { 574 | arr: FULL_SCANLINE_ARR, 575 | ccr: H_SYNC_CRR, 576 | has_pixels: true, 577 | }, 578 | Data { 579 | arr: FULL_SCANLINE_ARR, 580 | ccr: H_SYNC_CRR, 581 | has_pixels: true, 582 | }, 583 | Data { 584 | arr: FULL_SCANLINE_ARR, 585 | ccr: H_SYNC_CRR, 586 | has_pixels: true, 587 | }, 588 | Data { 589 | arr: FULL_SCANLINE_ARR, 590 | ccr: H_SYNC_CRR, 591 | has_pixels: true, 592 | }, 593 | Data { 594 | arr: FULL_SCANLINE_ARR, 595 | ccr: H_SYNC_CRR, 596 | has_pixels: true, 597 | }, 598 | Data { 599 | arr: FULL_SCANLINE_ARR, 600 | ccr: H_SYNC_CRR, 601 | has_pixels: true, 602 | }, 603 | Data { 604 | arr: FULL_SCANLINE_ARR, 605 | ccr: H_SYNC_CRR, 606 | has_pixels: true, 607 | }, 608 | Data { 609 | arr: FULL_SCANLINE_ARR, 610 | ccr: H_SYNC_CRR, 611 | has_pixels: true, 612 | }, 613 | Data { 614 | arr: FULL_SCANLINE_ARR, 615 | ccr: H_SYNC_CRR, 616 | has_pixels: true, 617 | }, 618 | Data { 619 | arr: FULL_SCANLINE_ARR, 620 | ccr: H_SYNC_CRR, 621 | has_pixels: true, 622 | }, 623 | Data { 624 | arr: FULL_SCANLINE_ARR, 625 | ccr: H_SYNC_CRR, 626 | has_pixels: true, 627 | }, 628 | Data { 629 | arr: FULL_SCANLINE_ARR, 630 | ccr: H_SYNC_CRR, 631 | has_pixels: true, 632 | }, 633 | Data { 634 | arr: FULL_SCANLINE_ARR, 635 | ccr: H_SYNC_CRR, 636 | has_pixels: true, 637 | }, 638 | Data { 639 | arr: FULL_SCANLINE_ARR, 640 | ccr: H_SYNC_CRR, 641 | has_pixels: true, 642 | }, 643 | Data { 644 | arr: FULL_SCANLINE_ARR, 645 | ccr: H_SYNC_CRR, 646 | has_pixels: true, 647 | }, 648 | Data { 649 | arr: FULL_SCANLINE_ARR, 650 | ccr: H_SYNC_CRR, 651 | has_pixels: true, 652 | }, 653 | Data { 654 | arr: FULL_SCANLINE_ARR, 655 | ccr: H_SYNC_CRR, 656 | has_pixels: true, 657 | }, 658 | Data { 659 | arr: FULL_SCANLINE_ARR, 660 | ccr: H_SYNC_CRR, 661 | has_pixels: true, 662 | }, 663 | Data { 664 | arr: FULL_SCANLINE_ARR, 665 | ccr: H_SYNC_CRR, 666 | has_pixels: true, 667 | }, 668 | Data { 669 | arr: FULL_SCANLINE_ARR, 670 | ccr: H_SYNC_CRR, 671 | has_pixels: true, 672 | }, 673 | Data { 674 | arr: FULL_SCANLINE_ARR, 675 | ccr: H_SYNC_CRR, 676 | has_pixels: true, 677 | }, 678 | Data { 679 | arr: FULL_SCANLINE_ARR, 680 | ccr: H_SYNC_CRR, 681 | has_pixels: true, 682 | }, 683 | Data { 684 | arr: FULL_SCANLINE_ARR, 685 | ccr: H_SYNC_CRR, 686 | has_pixels: true, 687 | }, 688 | Data { 689 | arr: FULL_SCANLINE_ARR, 690 | ccr: H_SYNC_CRR, 691 | has_pixels: true, 692 | }, 693 | Data { 694 | arr: FULL_SCANLINE_ARR, 695 | ccr: H_SYNC_CRR, 696 | has_pixels: true, 697 | }, 698 | Data { 699 | arr: FULL_SCANLINE_ARR, 700 | ccr: H_SYNC_CRR, 701 | has_pixels: true, 702 | }, 703 | Data { 704 | arr: FULL_SCANLINE_ARR, 705 | ccr: H_SYNC_CRR, 706 | has_pixels: true, 707 | }, 708 | Data { 709 | arr: FULL_SCANLINE_ARR, 710 | ccr: H_SYNC_CRR, 711 | has_pixels: true, 712 | }, 713 | Data { 714 | arr: FULL_SCANLINE_ARR, 715 | ccr: H_SYNC_CRR, 716 | has_pixels: true, 717 | }, 718 | Data { 719 | arr: FULL_SCANLINE_ARR, 720 | ccr: H_SYNC_CRR, 721 | has_pixels: true, 722 | }, 723 | Data { 724 | arr: FULL_SCANLINE_ARR, 725 | ccr: H_SYNC_CRR, 726 | has_pixels: true, 727 | }, 728 | Data { 729 | arr: FULL_SCANLINE_ARR, 730 | ccr: H_SYNC_CRR, 731 | has_pixels: true, 732 | }, 733 | Data { 734 | arr: FULL_SCANLINE_ARR, 735 | ccr: H_SYNC_CRR, 736 | has_pixels: true, 737 | }, 738 | Data { 739 | arr: FULL_SCANLINE_ARR, 740 | ccr: H_SYNC_CRR, 741 | has_pixels: true, 742 | }, 743 | Data { 744 | arr: FULL_SCANLINE_ARR, 745 | ccr: H_SYNC_CRR, 746 | has_pixels: true, 747 | }, 748 | Data { 749 | arr: FULL_SCANLINE_ARR, 750 | ccr: H_SYNC_CRR, 751 | has_pixels: true, 752 | }, 753 | Data { 754 | arr: FULL_SCANLINE_ARR, 755 | ccr: H_SYNC_CRR, 756 | has_pixels: true, 757 | }, 758 | Data { 759 | arr: FULL_SCANLINE_ARR, 760 | ccr: H_SYNC_CRR, 761 | has_pixels: true, 762 | }, 763 | Data { 764 | arr: FULL_SCANLINE_ARR, 765 | ccr: H_SYNC_CRR, 766 | has_pixels: true, 767 | }, 768 | Data { 769 | arr: FULL_SCANLINE_ARR, 770 | ccr: H_SYNC_CRR, 771 | has_pixels: true, 772 | }, 773 | Data { 774 | arr: FULL_SCANLINE_ARR, 775 | ccr: H_SYNC_CRR, 776 | has_pixels: true, 777 | }, 778 | Data { 779 | arr: FULL_SCANLINE_ARR, 780 | ccr: H_SYNC_CRR, 781 | has_pixels: true, 782 | }, 783 | Data { 784 | arr: FULL_SCANLINE_ARR, 785 | ccr: H_SYNC_CRR, 786 | has_pixels: true, 787 | }, 788 | Data { 789 | arr: FULL_SCANLINE_ARR, 790 | ccr: H_SYNC_CRR, 791 | has_pixels: true, 792 | }, 793 | Data { 794 | arr: FULL_SCANLINE_ARR, 795 | ccr: H_SYNC_CRR, 796 | has_pixels: true, 797 | }, 798 | Data { 799 | arr: FULL_SCANLINE_ARR, 800 | ccr: H_SYNC_CRR, 801 | has_pixels: true, 802 | }, 803 | Data { 804 | arr: FULL_SCANLINE_ARR, 805 | ccr: H_SYNC_CRR, 806 | has_pixels: true, 807 | }, 808 | Data { 809 | arr: FULL_SCANLINE_ARR, 810 | ccr: H_SYNC_CRR, 811 | has_pixels: true, 812 | }, 813 | Data { 814 | arr: FULL_SCANLINE_ARR, 815 | ccr: H_SYNC_CRR, 816 | has_pixels: true, 817 | }, 818 | Data { 819 | arr: FULL_SCANLINE_ARR, 820 | ccr: H_SYNC_CRR, 821 | has_pixels: true, 822 | }, 823 | Data { 824 | arr: FULL_SCANLINE_ARR, 825 | ccr: H_SYNC_CRR, 826 | has_pixels: true, 827 | }, 828 | Data { 829 | arr: FULL_SCANLINE_ARR, 830 | ccr: H_SYNC_CRR, 831 | has_pixels: true, 832 | }, 833 | Data { 834 | arr: FULL_SCANLINE_ARR, 835 | ccr: H_SYNC_CRR, 836 | has_pixels: true, 837 | }, 838 | Data { 839 | arr: FULL_SCANLINE_ARR, 840 | ccr: H_SYNC_CRR, 841 | has_pixels: true, 842 | }, 843 | Data { 844 | arr: FULL_SCANLINE_ARR, 845 | ccr: H_SYNC_CRR, 846 | has_pixels: true, 847 | }, 848 | Data { 849 | arr: FULL_SCANLINE_ARR, 850 | ccr: H_SYNC_CRR, 851 | has_pixels: true, 852 | }, 853 | Data { 854 | arr: FULL_SCANLINE_ARR, 855 | ccr: H_SYNC_CRR, 856 | has_pixels: true, 857 | }, 858 | Data { 859 | arr: FULL_SCANLINE_ARR, 860 | ccr: H_SYNC_CRR, 861 | has_pixels: true, 862 | }, 863 | Data { 864 | arr: FULL_SCANLINE_ARR, 865 | ccr: H_SYNC_CRR, 866 | has_pixels: true, 867 | }, 868 | Data { 869 | arr: FULL_SCANLINE_ARR, 870 | ccr: H_SYNC_CRR, 871 | has_pixels: true, 872 | }, 873 | Data { 874 | arr: FULL_SCANLINE_ARR, 875 | ccr: H_SYNC_CRR, 876 | has_pixels: true, 877 | }, 878 | Data { 879 | arr: FULL_SCANLINE_ARR, 880 | ccr: H_SYNC_CRR, 881 | has_pixels: true, 882 | }, 883 | Data { 884 | arr: FULL_SCANLINE_ARR, 885 | ccr: H_SYNC_CRR, 886 | has_pixels: true, 887 | }, 888 | Data { 889 | arr: FULL_SCANLINE_ARR, 890 | ccr: H_SYNC_CRR, 891 | has_pixels: true, 892 | }, 893 | Data { 894 | arr: FULL_SCANLINE_ARR, 895 | ccr: H_SYNC_CRR, 896 | has_pixels: true, 897 | }, 898 | Data { 899 | arr: FULL_SCANLINE_ARR, 900 | ccr: H_SYNC_CRR, 901 | has_pixels: true, 902 | }, 903 | Data { 904 | arr: FULL_SCANLINE_ARR, 905 | ccr: H_SYNC_CRR, 906 | has_pixels: true, 907 | }, 908 | Data { 909 | arr: FULL_SCANLINE_ARR, 910 | ccr: H_SYNC_CRR, 911 | has_pixels: true, 912 | }, 913 | Data { 914 | arr: FULL_SCANLINE_ARR, 915 | ccr: H_SYNC_CRR, 916 | has_pixels: true, 917 | }, 918 | Data { 919 | arr: FULL_SCANLINE_ARR, 920 | ccr: H_SYNC_CRR, 921 | has_pixels: true, 922 | }, 923 | Data { 924 | arr: FULL_SCANLINE_ARR, 925 | ccr: H_SYNC_CRR, 926 | has_pixels: true, 927 | }, 928 | Data { 929 | arr: FULL_SCANLINE_ARR, 930 | ccr: H_SYNC_CRR, 931 | has_pixels: true, 932 | }, 933 | Data { 934 | arr: FULL_SCANLINE_ARR, 935 | ccr: H_SYNC_CRR, 936 | has_pixels: true, 937 | }, 938 | Data { 939 | arr: FULL_SCANLINE_ARR, 940 | ccr: H_SYNC_CRR, 941 | has_pixels: true, 942 | }, 943 | Data { 944 | arr: FULL_SCANLINE_ARR, 945 | ccr: H_SYNC_CRR, 946 | has_pixels: true, 947 | }, 948 | Data { 949 | arr: FULL_SCANLINE_ARR, 950 | ccr: H_SYNC_CRR, 951 | has_pixels: true, 952 | }, 953 | Data { 954 | arr: FULL_SCANLINE_ARR, 955 | ccr: H_SYNC_CRR, 956 | has_pixels: true, 957 | }, 958 | Data { 959 | arr: FULL_SCANLINE_ARR, 960 | ccr: H_SYNC_CRR, 961 | has_pixels: true, 962 | }, 963 | Data { 964 | arr: FULL_SCANLINE_ARR, 965 | ccr: H_SYNC_CRR, 966 | has_pixels: true, 967 | }, 968 | Data { 969 | arr: FULL_SCANLINE_ARR, 970 | ccr: H_SYNC_CRR, 971 | has_pixels: true, 972 | }, 973 | Data { 974 | arr: FULL_SCANLINE_ARR, 975 | ccr: H_SYNC_CRR, 976 | has_pixels: true, 977 | }, 978 | Data { 979 | arr: FULL_SCANLINE_ARR, 980 | ccr: H_SYNC_CRR, 981 | has_pixels: true, 982 | }, 983 | Data { 984 | arr: FULL_SCANLINE_ARR, 985 | ccr: H_SYNC_CRR, 986 | has_pixels: true, 987 | }, 988 | Data { 989 | arr: FULL_SCANLINE_ARR, 990 | ccr: H_SYNC_CRR, 991 | has_pixels: true, 992 | }, 993 | Data { 994 | arr: FULL_SCANLINE_ARR, 995 | ccr: H_SYNC_CRR, 996 | has_pixels: true, 997 | }, 998 | Data { 999 | arr: FULL_SCANLINE_ARR, 1000 | ccr: H_SYNC_CRR, 1001 | has_pixels: true, 1002 | }, 1003 | Data { 1004 | arr: FULL_SCANLINE_ARR, 1005 | ccr: H_SYNC_CRR, 1006 | has_pixels: true, 1007 | }, 1008 | Data { 1009 | arr: FULL_SCANLINE_ARR, 1010 | ccr: H_SYNC_CRR, 1011 | has_pixels: true, 1012 | }, 1013 | Data { 1014 | arr: FULL_SCANLINE_ARR, 1015 | ccr: H_SYNC_CRR, 1016 | has_pixels: true, 1017 | }, 1018 | Data { 1019 | arr: FULL_SCANLINE_ARR, 1020 | ccr: H_SYNC_CRR, 1021 | has_pixels: true, 1022 | }, 1023 | Data { 1024 | arr: FULL_SCANLINE_ARR, 1025 | ccr: H_SYNC_CRR, 1026 | has_pixels: true, 1027 | }, 1028 | Data { 1029 | arr: FULL_SCANLINE_ARR, 1030 | ccr: H_SYNC_CRR, 1031 | has_pixels: true, 1032 | }, 1033 | Data { 1034 | arr: FULL_SCANLINE_ARR, 1035 | ccr: H_SYNC_CRR, 1036 | has_pixels: true, 1037 | }, 1038 | Data { 1039 | arr: FULL_SCANLINE_ARR, 1040 | ccr: H_SYNC_CRR, 1041 | has_pixels: true, 1042 | }, 1043 | Data { 1044 | arr: FULL_SCANLINE_ARR, 1045 | ccr: H_SYNC_CRR, 1046 | has_pixels: true, 1047 | }, 1048 | Data { 1049 | arr: FULL_SCANLINE_ARR, 1050 | ccr: H_SYNC_CRR, 1051 | has_pixels: true, 1052 | }, 1053 | Data { 1054 | arr: FULL_SCANLINE_ARR, 1055 | ccr: H_SYNC_CRR, 1056 | has_pixels: true, 1057 | }, 1058 | Data { 1059 | arr: FULL_SCANLINE_ARR, 1060 | ccr: H_SYNC_CRR, 1061 | has_pixels: true, 1062 | }, 1063 | Data { 1064 | arr: FULL_SCANLINE_ARR, 1065 | ccr: H_SYNC_CRR, 1066 | has_pixels: true, 1067 | }, 1068 | Data { 1069 | arr: FULL_SCANLINE_ARR, 1070 | ccr: H_SYNC_CRR, 1071 | has_pixels: true, 1072 | }, 1073 | Data { 1074 | arr: FULL_SCANLINE_ARR, 1075 | ccr: H_SYNC_CRR, 1076 | has_pixels: true, 1077 | }, 1078 | Data { 1079 | arr: FULL_SCANLINE_ARR, 1080 | ccr: H_SYNC_CRR, 1081 | has_pixels: true, 1082 | }, 1083 | Data { 1084 | arr: FULL_SCANLINE_ARR, 1085 | ccr: H_SYNC_CRR, 1086 | has_pixels: true, 1087 | }, 1088 | Data { 1089 | arr: FULL_SCANLINE_ARR, 1090 | ccr: H_SYNC_CRR, 1091 | has_pixels: true, 1092 | }, 1093 | Data { 1094 | arr: FULL_SCANLINE_ARR, 1095 | ccr: H_SYNC_CRR, 1096 | has_pixels: true, 1097 | }, 1098 | Data { 1099 | arr: FULL_SCANLINE_ARR, 1100 | ccr: H_SYNC_CRR, 1101 | has_pixels: true, 1102 | }, 1103 | Data { 1104 | arr: FULL_SCANLINE_ARR, 1105 | ccr: H_SYNC_CRR, 1106 | has_pixels: true, 1107 | }, 1108 | Data { 1109 | arr: FULL_SCANLINE_ARR, 1110 | ccr: H_SYNC_CRR, 1111 | has_pixels: true, 1112 | }, 1113 | Data { 1114 | arr: FULL_SCANLINE_ARR, 1115 | ccr: H_SYNC_CRR, 1116 | has_pixels: true, 1117 | }, 1118 | Data { 1119 | arr: FULL_SCANLINE_ARR, 1120 | ccr: H_SYNC_CRR, 1121 | has_pixels: true, 1122 | }, 1123 | Data { 1124 | arr: FULL_SCANLINE_ARR, 1125 | ccr: H_SYNC_CRR, 1126 | has_pixels: true, 1127 | }, 1128 | Data { 1129 | arr: FULL_SCANLINE_ARR, 1130 | ccr: H_SYNC_CRR, 1131 | has_pixels: true, 1132 | }, 1133 | Data { 1134 | arr: FULL_SCANLINE_ARR, 1135 | ccr: H_SYNC_CRR, 1136 | has_pixels: true, 1137 | }, 1138 | Data { 1139 | arr: FULL_SCANLINE_ARR, 1140 | ccr: H_SYNC_CRR, 1141 | has_pixels: true, 1142 | }, 1143 | Data { 1144 | arr: FULL_SCANLINE_ARR, 1145 | ccr: H_SYNC_CRR, 1146 | has_pixels: true, 1147 | }, 1148 | Data { 1149 | arr: FULL_SCANLINE_ARR, 1150 | ccr: H_SYNC_CRR, 1151 | has_pixels: true, 1152 | }, 1153 | Data { 1154 | arr: FULL_SCANLINE_ARR, 1155 | ccr: H_SYNC_CRR, 1156 | has_pixels: true, 1157 | }, 1158 | Data { 1159 | arr: FULL_SCANLINE_ARR, 1160 | ccr: H_SYNC_CRR, 1161 | has_pixels: true, 1162 | }, 1163 | Data { 1164 | arr: FULL_SCANLINE_ARR, 1165 | ccr: H_SYNC_CRR, 1166 | has_pixels: true, 1167 | }, 1168 | Data { 1169 | arr: FULL_SCANLINE_ARR, 1170 | ccr: H_SYNC_CRR, 1171 | has_pixels: true, 1172 | }, 1173 | Data { 1174 | arr: FULL_SCANLINE_ARR, 1175 | ccr: H_SYNC_CRR, 1176 | has_pixels: true, 1177 | }, 1178 | Data { 1179 | arr: FULL_SCANLINE_ARR, 1180 | ccr: H_SYNC_CRR, 1181 | has_pixels: true, 1182 | }, 1183 | Data { 1184 | arr: FULL_SCANLINE_ARR, 1185 | ccr: H_SYNC_CRR, 1186 | has_pixels: true, 1187 | }, 1188 | Data { 1189 | arr: FULL_SCANLINE_ARR, 1190 | ccr: H_SYNC_CRR, 1191 | has_pixels: true, 1192 | }, 1193 | Data { 1194 | arr: FULL_SCANLINE_ARR, 1195 | ccr: H_SYNC_CRR, 1196 | has_pixels: true, 1197 | }, 1198 | Data { 1199 | arr: FULL_SCANLINE_ARR, 1200 | ccr: H_SYNC_CRR, 1201 | has_pixels: true, 1202 | }, 1203 | Data { 1204 | arr: FULL_SCANLINE_ARR, 1205 | ccr: H_SYNC_CRR, 1206 | has_pixels: true, 1207 | }, 1208 | Data { 1209 | arr: FULL_SCANLINE_ARR, 1210 | ccr: H_SYNC_CRR, 1211 | has_pixels: true, 1212 | }, 1213 | Data { 1214 | arr: FULL_SCANLINE_ARR, 1215 | ccr: H_SYNC_CRR, 1216 | has_pixels: true, 1217 | }, 1218 | Data { 1219 | arr: FULL_SCANLINE_ARR, 1220 | ccr: H_SYNC_CRR, 1221 | has_pixels: true, 1222 | }, 1223 | Data { 1224 | arr: FULL_SCANLINE_ARR, 1225 | ccr: H_SYNC_CRR, 1226 | has_pixels: true, 1227 | }, 1228 | Data { 1229 | arr: FULL_SCANLINE_ARR, 1230 | ccr: H_SYNC_CRR, 1231 | has_pixels: true, 1232 | }, 1233 | Data { 1234 | arr: FULL_SCANLINE_ARR, 1235 | ccr: H_SYNC_CRR, 1236 | has_pixels: true, 1237 | }, 1238 | Data { 1239 | arr: FULL_SCANLINE_ARR, 1240 | ccr: H_SYNC_CRR, 1241 | has_pixels: true, 1242 | }, 1243 | Data { 1244 | arr: FULL_SCANLINE_ARR, 1245 | ccr: H_SYNC_CRR, 1246 | has_pixels: true, 1247 | }, 1248 | Data { 1249 | arr: FULL_SCANLINE_ARR, 1250 | ccr: H_SYNC_CRR, 1251 | has_pixels: true, 1252 | }, 1253 | Data { 1254 | arr: FULL_SCANLINE_ARR, 1255 | ccr: H_SYNC_CRR, 1256 | has_pixels: true, 1257 | }, 1258 | Data { 1259 | arr: FULL_SCANLINE_ARR, 1260 | ccr: H_SYNC_CRR, 1261 | has_pixels: true, 1262 | }, 1263 | Data { 1264 | arr: FULL_SCANLINE_ARR, 1265 | ccr: H_SYNC_CRR, 1266 | has_pixels: true, 1267 | }, 1268 | Data { 1269 | arr: FULL_SCANLINE_ARR, 1270 | ccr: H_SYNC_CRR, 1271 | has_pixels: true, 1272 | }, 1273 | Data { 1274 | arr: FULL_SCANLINE_ARR, 1275 | ccr: H_SYNC_CRR, 1276 | has_pixels: true, 1277 | }, 1278 | Data { 1279 | arr: FULL_SCANLINE_ARR, 1280 | ccr: H_SYNC_CRR, 1281 | has_pixels: true, 1282 | }, 1283 | Data { 1284 | arr: FULL_SCANLINE_ARR, 1285 | ccr: H_SYNC_CRR, 1286 | has_pixels: true, 1287 | }, 1288 | Data { 1289 | arr: FULL_SCANLINE_ARR, 1290 | ccr: H_SYNC_CRR, 1291 | has_pixels: true, 1292 | }, 1293 | Data { 1294 | arr: FULL_SCANLINE_ARR, 1295 | ccr: H_SYNC_CRR, 1296 | has_pixels: true, 1297 | }, 1298 | Data { 1299 | arr: FULL_SCANLINE_ARR, 1300 | ccr: H_SYNC_CRR, 1301 | has_pixels: true, 1302 | }, 1303 | Data { 1304 | arr: FULL_SCANLINE_ARR, 1305 | ccr: H_SYNC_CRR, 1306 | has_pixels: true, 1307 | }, 1308 | Data { 1309 | arr: FULL_SCANLINE_ARR, 1310 | ccr: H_SYNC_CRR, 1311 | has_pixels: true, 1312 | }, 1313 | Data { 1314 | arr: FULL_SCANLINE_ARR, 1315 | ccr: H_SYNC_CRR, 1316 | has_pixels: true, 1317 | }, 1318 | Data { 1319 | arr: FULL_SCANLINE_ARR, 1320 | ccr: H_SYNC_CRR, 1321 | has_pixels: true, 1322 | }, 1323 | Data { 1324 | arr: FULL_SCANLINE_ARR, 1325 | ccr: H_SYNC_CRR, 1326 | has_pixels: true, 1327 | }, 1328 | Data { 1329 | arr: FULL_SCANLINE_ARR, 1330 | ccr: H_SYNC_CRR, 1331 | has_pixels: true, 1332 | }, 1333 | Data { 1334 | arr: FULL_SCANLINE_ARR, 1335 | ccr: H_SYNC_CRR, 1336 | has_pixels: true, 1337 | }, 1338 | Data { 1339 | arr: FULL_SCANLINE_ARR, 1340 | ccr: H_SYNC_CRR, 1341 | has_pixels: true, 1342 | }, 1343 | Data { 1344 | arr: FULL_SCANLINE_ARR, 1345 | ccr: H_SYNC_CRR, 1346 | has_pixels: true, 1347 | }, 1348 | Data { 1349 | arr: FULL_SCANLINE_ARR, 1350 | ccr: H_SYNC_CRR, 1351 | has_pixels: true, 1352 | }, 1353 | Data { 1354 | arr: FULL_SCANLINE_ARR, 1355 | ccr: H_SYNC_CRR, 1356 | has_pixels: true, 1357 | }, 1358 | Data { 1359 | arr: FULL_SCANLINE_ARR, 1360 | ccr: H_SYNC_CRR, 1361 | has_pixels: true, 1362 | }, 1363 | Data { 1364 | arr: FULL_SCANLINE_ARR, 1365 | ccr: H_SYNC_CRR, 1366 | has_pixels: true, 1367 | }, 1368 | Data { 1369 | arr: FULL_SCANLINE_ARR, 1370 | ccr: H_SYNC_CRR, 1371 | has_pixels: true, 1372 | }, 1373 | Data { 1374 | arr: FULL_SCANLINE_ARR, 1375 | ccr: H_SYNC_CRR, 1376 | has_pixels: true, 1377 | }, 1378 | Data { 1379 | arr: FULL_SCANLINE_ARR, 1380 | ccr: H_SYNC_CRR, 1381 | has_pixels: true, 1382 | }, 1383 | Data { 1384 | arr: FULL_SCANLINE_ARR, 1385 | ccr: H_SYNC_CRR, 1386 | has_pixels: true, 1387 | }, 1388 | Data { 1389 | arr: FULL_SCANLINE_ARR, 1390 | ccr: H_SYNC_CRR, 1391 | has_pixels: true, 1392 | }, 1393 | Data { 1394 | arr: FULL_SCANLINE_ARR, 1395 | ccr: H_SYNC_CRR, 1396 | has_pixels: true, 1397 | }, 1398 | Data { 1399 | arr: FULL_SCANLINE_ARR, 1400 | ccr: H_SYNC_CRR, 1401 | has_pixels: true, 1402 | }, 1403 | Data { 1404 | arr: FULL_SCANLINE_ARR, 1405 | ccr: H_SYNC_CRR, 1406 | has_pixels: true, 1407 | }, 1408 | Data { 1409 | arr: FULL_SCANLINE_ARR, 1410 | ccr: H_SYNC_CRR, 1411 | has_pixels: true, 1412 | }, 1413 | Data { 1414 | arr: FULL_SCANLINE_ARR, 1415 | ccr: H_SYNC_CRR, 1416 | has_pixels: true, 1417 | }, 1418 | Data { 1419 | arr: FULL_SCANLINE_ARR, 1420 | ccr: H_SYNC_CRR, 1421 | has_pixels: true, 1422 | }, 1423 | Data { 1424 | arr: FULL_SCANLINE_ARR, 1425 | ccr: H_SYNC_CRR, 1426 | has_pixels: true, 1427 | }, 1428 | Data { 1429 | arr: FULL_SCANLINE_ARR, 1430 | ccr: H_SYNC_CRR, 1431 | has_pixels: true, 1432 | }, 1433 | Data { 1434 | arr: FULL_SCANLINE_ARR, 1435 | ccr: H_SYNC_CRR, 1436 | has_pixels: true, 1437 | }, 1438 | Data { 1439 | arr: FULL_SCANLINE_ARR, 1440 | ccr: H_SYNC_CRR, 1441 | has_pixels: true, 1442 | }, 1443 | Data { 1444 | arr: FULL_SCANLINE_ARR, 1445 | ccr: H_SYNC_CRR, 1446 | has_pixels: true, 1447 | }, 1448 | Data { 1449 | arr: FULL_SCANLINE_ARR, 1450 | ccr: H_SYNC_CRR, 1451 | has_pixels: true, 1452 | }, 1453 | Data { 1454 | arr: FULL_SCANLINE_ARR, 1455 | ccr: H_SYNC_CRR, 1456 | has_pixels: true, 1457 | }, 1458 | Data { 1459 | arr: FULL_SCANLINE_ARR, 1460 | ccr: H_SYNC_CRR, 1461 | has_pixels: true, 1462 | }, 1463 | Data { 1464 | arr: FULL_SCANLINE_ARR, 1465 | ccr: H_SYNC_CRR, 1466 | has_pixels: true, 1467 | }, 1468 | Data { 1469 | arr: FULL_SCANLINE_ARR, 1470 | ccr: H_SYNC_CRR, 1471 | has_pixels: true, 1472 | }, 1473 | Data { 1474 | arr: FULL_SCANLINE_ARR, 1475 | ccr: H_SYNC_CRR, 1476 | has_pixels: true, 1477 | }, 1478 | Data { 1479 | arr: FULL_SCANLINE_ARR, 1480 | ccr: H_SYNC_CRR, 1481 | has_pixels: true, 1482 | }, 1483 | Data { 1484 | arr: FULL_SCANLINE_ARR, 1485 | ccr: H_SYNC_CRR, 1486 | has_pixels: true, 1487 | }, 1488 | Data { 1489 | arr: FULL_SCANLINE_ARR, 1490 | ccr: H_SYNC_CRR, 1491 | has_pixels: true, 1492 | }, 1493 | Data { 1494 | arr: FULL_SCANLINE_ARR, 1495 | ccr: H_SYNC_CRR, 1496 | has_pixels: true, 1497 | }, 1498 | Data { 1499 | arr: FULL_SCANLINE_ARR, 1500 | ccr: H_SYNC_CRR, 1501 | has_pixels: true, 1502 | }, 1503 | Data { 1504 | arr: FULL_SCANLINE_ARR, 1505 | ccr: H_SYNC_CRR, 1506 | has_pixels: true, 1507 | }, 1508 | Data { 1509 | arr: FULL_SCANLINE_ARR, 1510 | ccr: H_SYNC_CRR, 1511 | has_pixels: true, 1512 | }, 1513 | Data { 1514 | arr: FULL_SCANLINE_ARR, 1515 | ccr: H_SYNC_CRR, 1516 | has_pixels: true, 1517 | }, 1518 | Data { 1519 | arr: FULL_SCANLINE_ARR, 1520 | ccr: H_SYNC_CRR, 1521 | has_pixels: true, 1522 | }, 1523 | Data { 1524 | arr: FULL_SCANLINE_ARR, 1525 | ccr: H_SYNC_CRR, 1526 | has_pixels: true, 1527 | }, 1528 | Data { 1529 | arr: FULL_SCANLINE_ARR, 1530 | ccr: H_SYNC_CRR, 1531 | has_pixels: true, 1532 | }, 1533 | Data { 1534 | arr: FULL_SCANLINE_ARR, 1535 | ccr: H_SYNC_CRR, 1536 | has_pixels: true, 1537 | }, 1538 | Data { 1539 | arr: FULL_SCANLINE_ARR, 1540 | ccr: H_SYNC_CRR, 1541 | has_pixels: true, 1542 | }, 1543 | Data { 1544 | arr: FULL_SCANLINE_ARR, 1545 | ccr: H_SYNC_CRR, 1546 | has_pixels: true, 1547 | }, 1548 | Data { 1549 | arr: FULL_SCANLINE_ARR, 1550 | ccr: H_SYNC_CRR, 1551 | has_pixels: true, 1552 | }, 1553 | Data { 1554 | arr: FULL_SCANLINE_ARR, 1555 | ccr: H_SYNC_CRR, 1556 | has_pixels: true, 1557 | }, 1558 | Data { 1559 | arr: FULL_SCANLINE_ARR, 1560 | ccr: H_SYNC_CRR, 1561 | has_pixels: true, 1562 | }, 1563 | Data { 1564 | arr: FULL_SCANLINE_ARR, 1565 | ccr: H_SYNC_CRR, 1566 | has_pixels: true, 1567 | }, 1568 | Data { 1569 | arr: FULL_SCANLINE_ARR, 1570 | ccr: H_SYNC_CRR, 1571 | has_pixels: true, 1572 | }, 1573 | Data { 1574 | arr: FULL_SCANLINE_ARR, 1575 | ccr: H_SYNC_CRR, 1576 | has_pixels: true, 1577 | }, 1578 | Data { 1579 | arr: FULL_SCANLINE_ARR, 1580 | ccr: H_SYNC_CRR, 1581 | has_pixels: true, 1582 | }, 1583 | Data { 1584 | arr: FULL_SCANLINE_ARR, 1585 | ccr: H_SYNC_CRR, 1586 | has_pixels: true, 1587 | }, 1588 | Data { 1589 | arr: FULL_SCANLINE_ARR, 1590 | ccr: H_SYNC_CRR, 1591 | has_pixels: true, 1592 | }, 1593 | Data { 1594 | arr: FULL_SCANLINE_ARR, 1595 | ccr: H_SYNC_CRR, 1596 | has_pixels: true, 1597 | }, 1598 | Data { 1599 | arr: FULL_SCANLINE_ARR, 1600 | ccr: H_SYNC_CRR, 1601 | has_pixels: true, 1602 | }, 1603 | Data { 1604 | arr: FULL_SCANLINE_ARR, 1605 | ccr: H_SYNC_CRR, 1606 | has_pixels: true, 1607 | }, 1608 | Data { 1609 | arr: FULL_SCANLINE_ARR, 1610 | ccr: H_SYNC_CRR, 1611 | has_pixels: true, 1612 | }, 1613 | Data { 1614 | arr: FULL_SCANLINE_ARR, 1615 | ccr: H_SYNC_CRR, 1616 | has_pixels: true, 1617 | }, 1618 | Data { 1619 | arr: FULL_SCANLINE_ARR, 1620 | ccr: H_SYNC_CRR, 1621 | has_pixels: true, 1622 | }, 1623 | Data { 1624 | arr: FULL_SCANLINE_ARR, 1625 | ccr: H_SYNC_CRR, 1626 | has_pixels: true, 1627 | }, 1628 | Data { 1629 | arr: FULL_SCANLINE_ARR, 1630 | ccr: H_SYNC_CRR, 1631 | has_pixels: true, 1632 | }, 1633 | Data { 1634 | arr: FULL_SCANLINE_ARR, 1635 | ccr: H_SYNC_CRR, 1636 | has_pixels: true, 1637 | }, 1638 | Data { 1639 | arr: FULL_SCANLINE_ARR, 1640 | ccr: H_SYNC_CRR, 1641 | has_pixels: true, 1642 | }, 1643 | Data { 1644 | arr: FULL_SCANLINE_ARR, 1645 | ccr: H_SYNC_CRR, 1646 | has_pixels: true, 1647 | }, 1648 | Data { 1649 | arr: FULL_SCANLINE_ARR, 1650 | ccr: H_SYNC_CRR, 1651 | has_pixels: true, 1652 | }, 1653 | Data { 1654 | arr: FULL_SCANLINE_ARR, 1655 | ccr: H_SYNC_CRR, 1656 | has_pixels: true, 1657 | }, 1658 | Data { 1659 | arr: FULL_SCANLINE_ARR, 1660 | ccr: H_SYNC_CRR, 1661 | has_pixels: true, 1662 | }, 1663 | Data { 1664 | arr: FULL_SCANLINE_ARR, 1665 | ccr: H_SYNC_CRR, 1666 | has_pixels: true, 1667 | }, 1668 | Data { 1669 | arr: FULL_SCANLINE_ARR, 1670 | ccr: H_SYNC_CRR, 1671 | has_pixels: true, 1672 | }, 1673 | Data { 1674 | arr: FULL_SCANLINE_ARR, 1675 | ccr: H_SYNC_CRR, 1676 | has_pixels: true, 1677 | }, 1678 | Data { 1679 | arr: FULL_SCANLINE_ARR, 1680 | ccr: H_SYNC_CRR, 1681 | has_pixels: true, 1682 | }, 1683 | Data { 1684 | arr: FULL_SCANLINE_ARR, 1685 | ccr: H_SYNC_CRR, 1686 | has_pixels: true, 1687 | }, 1688 | Data { 1689 | arr: FULL_SCANLINE_ARR, 1690 | ccr: H_SYNC_CRR, 1691 | has_pixels: true, 1692 | }, 1693 | Data { 1694 | arr: FULL_SCANLINE_ARR, 1695 | ccr: H_SYNC_CRR, 1696 | has_pixels: true, 1697 | }, 1698 | Data { 1699 | arr: FULL_SCANLINE_ARR, 1700 | ccr: H_SYNC_CRR, 1701 | has_pixels: true, 1702 | }, 1703 | Data { 1704 | arr: FULL_SCANLINE_ARR, 1705 | ccr: H_SYNC_CRR, 1706 | has_pixels: true, 1707 | }, 1708 | Data { 1709 | arr: FULL_SCANLINE_ARR, 1710 | ccr: H_SYNC_CRR, 1711 | has_pixels: true, 1712 | }, 1713 | Data { 1714 | arr: FULL_SCANLINE_ARR, 1715 | ccr: H_SYNC_CRR, 1716 | has_pixels: true, 1717 | }, 1718 | Data { 1719 | arr: FULL_SCANLINE_ARR, 1720 | ccr: H_SYNC_CRR, 1721 | has_pixels: true, 1722 | }, 1723 | Data { 1724 | arr: FULL_SCANLINE_ARR, 1725 | ccr: H_SYNC_CRR, 1726 | has_pixels: true, 1727 | }, 1728 | Data { 1729 | arr: FULL_SCANLINE_ARR, 1730 | ccr: H_SYNC_CRR, 1731 | has_pixels: true, 1732 | }, 1733 | Data { 1734 | arr: FULL_SCANLINE_ARR, 1735 | ccr: H_SYNC_CRR, 1736 | has_pixels: true, 1737 | }, 1738 | Data { 1739 | arr: FULL_SCANLINE_ARR, 1740 | ccr: H_SYNC_CRR, 1741 | has_pixels: true, 1742 | }, 1743 | Data { 1744 | arr: FULL_SCANLINE_ARR, 1745 | ccr: H_SYNC_CRR, 1746 | has_pixels: true, 1747 | }, 1748 | Data { 1749 | arr: FULL_SCANLINE_ARR, 1750 | ccr: H_SYNC_CRR, 1751 | has_pixels: true, 1752 | }, 1753 | Data { 1754 | arr: FULL_SCANLINE_ARR, 1755 | ccr: H_SYNC_CRR, 1756 | has_pixels: true, 1757 | }, 1758 | Data { 1759 | arr: FULL_SCANLINE_ARR, 1760 | ccr: H_SYNC_CRR, 1761 | has_pixels: true, 1762 | }, 1763 | Data { 1764 | arr: FULL_SCANLINE_ARR, 1765 | ccr: H_SYNC_CRR, 1766 | has_pixels: true, 1767 | }, 1768 | Data { 1769 | arr: FULL_SCANLINE_ARR, 1770 | ccr: H_SYNC_CRR, 1771 | has_pixels: true, 1772 | }, 1773 | Data { 1774 | arr: FULL_SCANLINE_ARR, 1775 | ccr: H_SYNC_CRR, 1776 | has_pixels: true, 1777 | }, 1778 | Data { 1779 | arr: FULL_SCANLINE_ARR, 1780 | ccr: H_SYNC_CRR, 1781 | has_pixels: true, 1782 | }, 1783 | Data { 1784 | arr: FULL_SCANLINE_ARR, 1785 | ccr: H_SYNC_CRR, 1786 | has_pixels: true, 1787 | }, 1788 | Data { 1789 | arr: FULL_SCANLINE_ARR, 1790 | ccr: H_SYNC_CRR, 1791 | has_pixels: true, 1792 | }, 1793 | Data { 1794 | arr: FULL_SCANLINE_ARR, 1795 | ccr: H_SYNC_CRR, 1796 | has_pixels: true, 1797 | }, 1798 | Data { 1799 | arr: FULL_SCANLINE_ARR, 1800 | ccr: H_SYNC_CRR, 1801 | has_pixels: true, 1802 | }, 1803 | Data { 1804 | arr: FULL_SCANLINE_ARR, 1805 | ccr: H_SYNC_CRR, 1806 | has_pixels: true, 1807 | }, 1808 | Data { 1809 | arr: FULL_SCANLINE_ARR, 1810 | ccr: H_SYNC_CRR, 1811 | has_pixels: true, 1812 | }, 1813 | Data { 1814 | arr: FULL_SCANLINE_ARR, 1815 | ccr: H_SYNC_CRR, 1816 | has_pixels: true, 1817 | }, 1818 | Data { 1819 | arr: FULL_SCANLINE_ARR, 1820 | ccr: H_SYNC_CRR, 1821 | has_pixels: true, 1822 | }, 1823 | Data { 1824 | arr: FULL_SCANLINE_ARR, 1825 | ccr: H_SYNC_CRR, 1826 | has_pixels: true, 1827 | }, 1828 | Data { 1829 | arr: FULL_SCANLINE_ARR, 1830 | ccr: H_SYNC_CRR, 1831 | has_pixels: true, 1832 | }, 1833 | Data { 1834 | arr: FULL_SCANLINE_ARR, 1835 | ccr: H_SYNC_CRR, 1836 | has_pixels: true, 1837 | }, 1838 | Data { 1839 | arr: FULL_SCANLINE_ARR, 1840 | ccr: H_SYNC_CRR, 1841 | has_pixels: true, 1842 | }, 1843 | Data { 1844 | arr: FULL_SCANLINE_ARR, 1845 | ccr: H_SYNC_CRR, 1846 | has_pixels: true, 1847 | }, 1848 | Data { 1849 | arr: FULL_SCANLINE_ARR, 1850 | ccr: H_SYNC_CRR, 1851 | has_pixels: true, 1852 | }, 1853 | Data { 1854 | arr: FULL_SCANLINE_ARR, 1855 | ccr: H_SYNC_CRR, 1856 | has_pixels: true, 1857 | }, 1858 | Data { 1859 | arr: FULL_SCANLINE_ARR, 1860 | ccr: H_SYNC_CRR, 1861 | has_pixels: true, 1862 | }, 1863 | Data { 1864 | arr: FULL_SCANLINE_ARR, 1865 | ccr: H_SYNC_CRR, 1866 | has_pixels: true, 1867 | }, 1868 | // scanline 310 - 312 1869 | Data { 1870 | arr: HALF_SCANLINE_ARR, 1871 | ccr: SHORT_SYNC_CRR, 1872 | has_pixels: false, 1873 | }, 1874 | Data { 1875 | arr: HALF_SCANLINE_ARR, 1876 | ccr: SHORT_SYNC_CRR, 1877 | has_pixels: false, 1878 | }, 1879 | Data { 1880 | arr: HALF_SCANLINE_ARR, 1881 | ccr: SHORT_SYNC_CRR, 1882 | has_pixels: false, 1883 | }, 1884 | Data { 1885 | arr: HALF_SCANLINE_ARR, 1886 | ccr: SHORT_SYNC_CRR, 1887 | has_pixels: false, 1888 | }, 1889 | Data { 1890 | arr: HALF_SCANLINE_ARR, 1891 | ccr: SHORT_SYNC_CRR, 1892 | has_pixels: false, 1893 | }, 1894 | Data { 1895 | arr: HALF_SCANLINE_ARR, 1896 | ccr: SHORT_SYNC_CRR, 1897 | has_pixels: false, 1898 | }, 1899 | ]; 1900 | --------------------------------------------------------------------------------