From 40867f7b56e82b4d3d78a5aa545988ddc8f9b2e8 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Tue, 10 Aug 2021 20:32:04 +0100 Subject: [PATCH 01/41] WIP video output. I'm porting over from the scanvideo examples in the pico-examples repo. --- .cargo/config.toml | 2 + src/main.rs | 136 +++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 132 insertions(+), 6 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 703b7b7..eeb17d7 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,4 +1,6 @@ [target.thumbv6m-none-eabi] +runner = "elf2uf2-rs -d" + rustflags = [ # This is needed if your flash or ram addresses are not aligned to 0x10000 in memory.x # See https://github.com/rust-embedded/cortex-m-quickstart/pull/95 diff --git a/src/main.rs b/src/main.rs index 3de2184..4ae4925 100644 --- a/src/main.rs +++ b/src/main.rs @@ -37,8 +37,12 @@ // Imports // ----------------------------------------------------------------------------- -use cortex_m_rt::entry; +use core::sync::atomic::AtomicBool; +use core::sync::atomic::Ordering; +use core::sync::atomic::AtomicU32; + use embedded_hal::digital::v2::OutputPin; +use cortex_m_rt::entry; use embedded_time::rate::*; use hal::clocks::Clock; use panic_halt as _; @@ -97,7 +101,7 @@ fn main() -> ! { // Create an object we can use to busy-wait for specified numbers of // milliseconds. For this to work, it needs to know our clock speed. - let mut delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().integer()); + let _delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().integer()); // sio is the *Single-cycle Input/Output* peripheral. It has all our GPIO // pins, as well as some mailboxes and other useful things for inter-core @@ -114,16 +118,136 @@ fn main() -> ! { // Grab the LED pin let mut led_pin = pins.led.into_push_pull_output(); + // Give H-Sync, V-Sync and 12 RGB colour pins to PIO0 to output video + let _h_sync = pins.gpio0.into_mode::(); + let _v_sync = pins.gpio1.into_mode::(); + let _red0 = pins.gpio2.into_mode::(); + let _red1 = pins.gpio3.into_mode::(); + let _red2 = pins.gpio4.into_mode::(); + let _red3 = pins.gpio5.into_mode::(); + let _green0 = pins.gpio6.into_mode::(); + let _green1 = pins.gpio7.into_mode::(); + let _green2 = pins.gpio8.into_mode::(); + let _green3 = pins.gpio9.into_mode::(); + let _blue0 = pins.gpio10.into_mode::(); + let _blue1 = pins.gpio11.into_mode::(); + let _blue2 = pins.gpio12.into_mode::(); + let _blue3 = pins.gpio13.into_mode::(); + + scanvideo_setup(); + + scanvideo_timing_enable(true); // Do some blinky so we can see it work. loop { - led_pin.set_high().unwrap(); - delay.delay_ms(500); - led_pin.set_low().unwrap(); - delay.delay_ms(500); + led_pin.set_low().unwrap(); + let mut scanline_buffer = scanvideo_begin_scanline_generation(true); + draw_color_bar(&mut scanline_buffer); + scanvideo_end_scanline_generation(scanline_buffer); + led_pin.set_high().unwrap(); + } +} + +const VIDEO_CLOCK_FREQUENCY: u32 = 25_000_000; +const VIDEO_VISIBLE_PIXELS: u32 = 640; +const VIDEO_VISIBLE_LINES: u32 = 480; +const VIDEO_H_FRONT_PORCH: u32 = 16; +const VIDEO_H_PULSE: u32 = 64; +const VIDEO_H_TOTAL: u32 = 800; +const VIDEO_H_SYNC_POLARITY: bool = true; +const VIDEO_V_FRONT_PORCH: u32 = 1; +const VIDEO_V_PULSE: u32 = 2; +const VIDEO_V_TOTAL: u32 = 523; +const VIDEO_V_SYNC_POLARITY: bool = true; + +const MAX_SCANVIDEO_WORDS: usize = 1 + ((VIDEO_VISIBLE_PIXELS as usize) / 2); +const MAX_SCANVIDEO_BUFFERS: usize = 8; + +struct Buffer { + pio_data: [u32; MAX_SCANVIDEO_WORDS], +} + +impl Buffer { + const fn new() -> Buffer { + Buffer { + pio_data: [0; MAX_SCANVIDEO_WORDS] + } + } +} + +struct Buffers { + buffers: [Buffer; MAX_SCANVIDEO_BUFFERS], +} + +struct BufferHandle { + buffer: &'static mut Buffer +} + +struct ScanvideoState { + // scanline.lock + // dma.lock + // free_list.lock + // in_use.lock + // scanline.last_scanline_id + last_scanline_id: AtomicU32, +} + +static SHARED_STATE: ScanvideoState = ScanvideoState { + last_scanline_id: AtomicU32::new(0) +}; + +/// Configure video for 640x480 @ 60 Hz +fn scanvideo_setup() { + SHARED_STATE.last_scanline_id.store(0xFFFFFFFF, Ordering::Relaxed); + + // video_program_load_offset = pio_add_program(video_pio, &video_24mhz_composable); + // setup_sm(PICO_SCANVIDEO_SCANLINE_SM, video_program_load_offset); + // video_htiming_load_offset = pio_add_program(video_pio, &video_timing_program); + // setup_sm(PICO_SCANVIDEO_TIMING_SM, video_htiming_load_offset); + // irq_set_priority(PIO0_IRQ_0, 0); // highest priority + // irq_set_priority(PIO0_IRQ_1, 0x40); // lower priority by 1 + +} + +fn scanvideo_timing_enable(_some_arg: bool) { + +} + +static mut BUFFERS: Buffers = Buffers { + buffers: [ + Buffer::new(), + Buffer::new(), + Buffer::new(), + Buffer::new(), + Buffer::new(), + Buffer::new(), + Buffer::new(), + Buffer::new(), + ] +}; + +fn scanvideo_begin_scanline_generation(_some_arg: bool) -> BufferHandle { + let next_line = SHARED_STATE.last_scanline_id.load(Ordering::Relaxed); + let buffer = (next_line as usize) % MAX_SCANVIDEO_BUFFERS; + let buffer = unsafe { &mut BUFFERS.buffers[buffer] }; + BufferHandle { + buffer } } +fn draw_color_bar(handle: &mut BufferHandle) { + handle.buffer.pio_data[0] = VIDEO_VISIBLE_PIXELS; + for p in handle.buffer.pio_data[1..].iter_mut() { + // Each word is two 16-bit values. + // Of those, we use the bottom 12-bits as the RGB colour. + // White pixel, black pixel. + *p = 0x0FFF_0000; + } +} + +fn scanvideo_end_scanline_generation(_complete_buffer: BufferHandle) { +} + // ----------------------------------------------------------------------------- // End of file // ----------------------------------------------------------------------------- From 1833b4d598c57edcab0ef2c6ebf888a0c2b1513d Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Mon, 25 Oct 2021 16:23:04 +0100 Subject: [PATCH 02/41] Bump to latest c-m-rt --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5b57a5e..5bdcbfc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,11 +8,11 @@ version = "0.2.0" [dependencies] # Useful Cortex-M specific functions (e.g. SysTick) -cortex-m = "0.7.3" +cortex-m = "0.7" # The Raspberry Pi Pico HAL pico = { git = "https://github.com/rp-rs/rp-hal.git" } # Cortex-M run-time (or start-up) code -cortex-m-rt = "0.6.14" +cortex-m-rt = "0.7" # The BIOS API we export to the OS neotron-common-bios = { git = "https://github.com/Neotron-Compute/Neotron-Common-BIOS.git" } # For time keeping/handling From a41437196e5c5cd10f421529cb00371d0abf8f4c Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Sat, 30 Oct 2021 20:32:05 +0100 Subject: [PATCH 03/41] Starting again from first principles. Seems to generate H-Sync and V-Sync. Clock speed needs adjusting up from 125 MHz to 126 MHz to get the timing better though. --- Cargo.toml | 6 +- src/main.rs | 198 +++++++++++++++++++++++----------------------------- 2 files changed, 93 insertions(+), 111 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5bdcbfc..e6a9d55 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] authors = ["Jonathan 'theJPster' Pallant "] -edition = "2018" +edition = "2021" readme = "README.md" license = "GPL-3.0-or-later" name = "neotron-pico-bios" @@ -29,6 +29,10 @@ defmt-rtt = "0.2" panic-probe = "0.2" # Fetches the BIOS version from git git-version = "0.3" +# RP2040 PIO driver +pio = { git = "https://github.com/rp-rs/pio-rs.git", branch = "main" } +# RP2040 PIO driver macros +pio-proc = { git = "https://github.com/rp-rs/pio-rs.git", branch = "main" } [features] default = [ diff --git a/src/main.rs b/src/main.rs index 605cdd9..df901e9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -37,11 +37,6 @@ // Imports // ----------------------------------------------------------------------------- -use core::sync::atomic::AtomicBool; -use core::sync::atomic::AtomicU32; -use core::sync::atomic::Ordering; - -use cortex_m_rt::entry; use cortex_m_rt::entry; use defmt::*; use defmt_rtt as _; @@ -50,9 +45,7 @@ use embedded_time::rate::*; use git_version::git_version; use hal::clocks::Clock; use panic_probe as _; -use pico; -use pico::hal; -use pico::hal::pac; +use pico::{self, hal::{self, pac, pio::{self as hal_pio, PIOExt}}}; // ----------------------------------------------------------------------------- // Static and Const Data @@ -112,7 +105,7 @@ fn main() -> ! { // Create an object we can use to busy-wait for specified numbers of // milliseconds. For this to work, it needs to know our clock speed. - let _delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().integer()); + let mut delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().integer()); // sio is the *Single-cycle Input/Output* peripheral. It has all our GPIO // pins, as well as some mailboxes and other useful things for inter-core @@ -147,116 +140,101 @@ fn main() -> ! { let _blue2 = pins.gpio12.into_mode::(); let _blue3 = pins.gpio13.into_mode::(); - scanvideo_setup(); - - scanvideo_timing_enable(true); - // Do some blinky so we can see it work. + // Need to configure PIO0SM0 to run the timing loop. We post timing data + // and it busy-waits the appropriate amount of time and trigger the + // appropriate interrupts. + + // Post + + // Need to configure PIO0SM1 to run the pixel loop. It waits for an IRQ + // (posted by the timing loop) then pulls pixel data from the FIFO. We + // post the number of pixels for that line, then the pixel data. + + // Post ; each is a 12-bit value padded up to 16-bits. + + let (mut pio, sm0, _sm1, _sm2, _sm3) = pac.PIO0.split(&mut pac.RESETS); + + let program = pio_proc::pio!(32, + " + ; Block until FIFO receives data + pull block + .wrap_target + ; Note autopull is set to 32-bits + ; output is set to shift right + out exec, 16 ; Execute top 16-bits as an instruction + out pins, 2 ; Push bottom 2 bits to PINS + out x, 14 ; Push next 14 bits into X for the timing loop + loop0: + jmp x-- loop0 ; Spin until X is zero + .wrap + "); + + let installed = pio.install(&program.program).unwrap(); + let (mut sm, _rx_fifo, mut tx_fifo) = hal_pio::PIOBuilder::from_program(installed) + .buffers(hal_pio::Buffers::RxTx) + .set_pins(0, 2) + .out_pins(0, 2) + .clock_divisor(5.0) + .autopull(true) + .out_shift_direction(hal_pio::ShiftDirection::Right) + .pull_threshold(32) + .build(sm0); + sm.set_pindirs([(0, hal_pio::PinDir::Output),(1, hal_pio::PinDir::Output)]); + + sm.start(); + + let mut x: u8 = 0; loop { - led_pin.set_low().unwrap(); - let mut scanline_buffer = scanvideo_begin_scanline_generation(true); - draw_color_bar(&mut scanline_buffer); - scanvideo_end_scanline_generation(scanline_buffer); - led_pin.set_high().unwrap(); - } -} - -const VIDEO_CLOCK_FREQUENCY: u32 = 25_000_000; -const VIDEO_VISIBLE_PIXELS: u32 = 640; -const VIDEO_VISIBLE_LINES: u32 = 480; -const VIDEO_H_FRONT_PORCH: u32 = 16; -const VIDEO_H_PULSE: u32 = 64; -const VIDEO_H_TOTAL: u32 = 800; -const VIDEO_H_SYNC_POLARITY: bool = true; -const VIDEO_V_FRONT_PORCH: u32 = 1; -const VIDEO_V_PULSE: u32 = 2; -const VIDEO_V_TOTAL: u32 = 523; -const VIDEO_V_SYNC_POLARITY: bool = true; - -const MAX_SCANVIDEO_WORDS: usize = 1 + ((VIDEO_VISIBLE_PIXELS as usize) / 2); -const MAX_SCANVIDEO_BUFFERS: usize = 8; - -struct Buffer { - pio_data: [u32; MAX_SCANVIDEO_WORDS], -} - -impl Buffer { - const fn new() -> Buffer { - Buffer { - pio_data: [0; MAX_SCANVIDEO_WORDS], + // Flash a pin every 60 frames, or once a second + x = x.wrapping_add(1); + if x == 60 { + led_pin.set_high().unwrap(); + x = 0; + } else { + led_pin.set_low().unwrap(); + } + // This should all be moved off into an IRQ so it doesn't block the main thread + for scanline in 1..=525 { + // 640 x 480, negative h-sync - four periods per video line + + let need_vsync = scanline >= 490 && scanline <= 491; + let _is_visible = scanline <= 480; + + // This if the front porch + load_timing(&mut tx_fifo, 16, true, need_vsync, false); + // This is the sync pulse + load_timing(&mut tx_fifo, 96, false, need_vsync, false); + // This is the back porch + load_timing(&mut tx_fifo, 48, true, need_vsync, false); + // This is the visible portion - trigger the IRQ to start pixels moving + load_timing(&mut tx_fifo, 640, true, need_vsync, true); } } } -struct Buffers { - buffers: [Buffer; MAX_SCANVIDEO_BUFFERS], -} - -struct BufferHandle { - buffer: &'static mut Buffer, -} - -struct ScanvideoState { - // scanline.lock - // dma.lock - // free_list.lock - // in_use.lock - // scanline.last_scanline_id - last_scanline_id: AtomicU32, -} - -static SHARED_STATE: ScanvideoState = ScanvideoState { - last_scanline_id: AtomicU32::new(0), -}; - -/// Configure video for 640x480 @ 60 Hz -fn scanvideo_setup() { - SHARED_STATE - .last_scanline_id - .store(0xFFFFFFFF, Ordering::Relaxed); - - // video_program_load_offset = pio_add_program(video_pio, &video_24mhz_composable); - // setup_sm(PICO_SCANVIDEO_SCANLINE_SM, video_program_load_offset); - // video_htiming_load_offset = pio_add_program(video_pio, &video_timing_program); - // setup_sm(PICO_SCANVIDEO_TIMING_SM, video_htiming_load_offset); - // irq_set_priority(PIO0_IRQ_0, 0); // highest priority - // irq_set_priority(PIO0_IRQ_1, 0x40); // lower priority by 1 -} - -fn scanvideo_timing_enable(_some_arg: bool) {} - -static mut BUFFERS: Buffers = Buffers { - buffers: [ - Buffer::new(), - Buffer::new(), - Buffer::new(), - Buffer::new(), - Buffer::new(), - Buffer::new(), - Buffer::new(), - Buffer::new(), - ], -}; - -fn scanvideo_begin_scanline_generation(_some_arg: bool) -> BufferHandle { - let next_line = SHARED_STATE.last_scanline_id.load(Ordering::Relaxed); - let buffer = (next_line as usize) % MAX_SCANVIDEO_BUFFERS; - let buffer = unsafe { &mut BUFFERS.buffers[buffer] }; - BufferHandle { buffer } -} - -fn draw_color_bar(handle: &mut BufferHandle) { - handle.buffer.pio_data[0] = VIDEO_VISIBLE_PIXELS; - for p in handle.buffer.pio_data[1..].iter_mut() { - // Each word is two 16-bit values. - // Of those, we use the bottom 12-bits as the RGB colour. - // White pixel, black pixel. - *p = 0x0FFF_0000; +fn load_timing(fifo: &mut hal_pio::Tx<(pac::PIO0, hal_pio::SM0)>, period: u16, hsync: bool, vsync: bool, want_irq: bool) { + let command = if want_irq { + // This command sets IRQ 0 + pio::InstructionOperands::IRQ { clear: false, wait: false, index: 0, relative: false } + } + else { + // This command is a no-op (it moves Y into Y) + pio::InstructionOperands::MOV { destination: pio::MovDestination::Y, op: pio::MovOperation::None, source: pio::MovSource::Y } + }; + let mut value: u32 = u32::from(command.encode()); + if hsync { + value |= 1 << 16; + } + if vsync { + value |= 1 << 17; + } + value |= (u32::from(period) - 4) << 18; + while !fifo.write(value) { + // Spin? } } -fn scanvideo_end_scanline_generation(_complete_buffer: BufferHandle) {} - // ----------------------------------------------------------------------------- // End of file // ----------------------------------------------------------------------------- From d50160eef61185b14fdf58c155024317f2067d72 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Sat, 30 Oct 2021 20:32:41 +0100 Subject: [PATCH 04/41] Formatting! --- src/main.rs | 47 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/src/main.rs b/src/main.rs index df901e9..0981074 100644 --- a/src/main.rs +++ b/src/main.rs @@ -45,7 +45,13 @@ use embedded_time::rate::*; use git_version::git_version; use hal::clocks::Clock; use panic_probe as _; -use pico::{self, hal::{self, pac, pio::{self as hal_pio, PIOExt}}}; +use pico::{ + self, + hal::{ + self, pac, + pio::{self as hal_pio, PIOExt}, + }, +}; // ----------------------------------------------------------------------------- // Static and Const Data @@ -140,7 +146,6 @@ fn main() -> ! { let _blue2 = pins.gpio12.into_mode::(); let _blue3 = pins.gpio13.into_mode::(); - // Need to configure PIO0SM0 to run the timing loop. We post timing data // and it busy-waits the appropriate amount of time and trigger the // appropriate interrupts. @@ -155,7 +160,8 @@ fn main() -> ! { let (mut pio, sm0, _sm1, _sm2, _sm3) = pac.PIO0.split(&mut pac.RESETS); - let program = pio_proc::pio!(32, + let program = pio_proc::pio!( + 32, " ; Block until FIFO receives data pull block @@ -168,7 +174,8 @@ fn main() -> ! { loop0: jmp x-- loop0 ; Spin until X is zero .wrap - "); + " + ); let installed = pio.install(&program.program).unwrap(); let (mut sm, _rx_fifo, mut tx_fifo) = hal_pio::PIOBuilder::from_program(installed) @@ -180,7 +187,7 @@ fn main() -> ! { .out_shift_direction(hal_pio::ShiftDirection::Right) .pull_threshold(32) .build(sm0); - sm.set_pindirs([(0, hal_pio::PinDir::Output),(1, hal_pio::PinDir::Output)]); + sm.set_pindirs([(0, hal_pio::PinDir::Output), (1, hal_pio::PinDir::Output)]); sm.start(); @@ -197,10 +204,10 @@ fn main() -> ! { // This should all be moved off into an IRQ so it doesn't block the main thread for scanline in 1..=525 { // 640 x 480, negative h-sync - four periods per video line - - let need_vsync = scanline >= 490 && scanline <= 491; + + let need_vsync = scanline >= 490 && scanline <= 491; let _is_visible = scanline <= 480; - + // This if the front porch load_timing(&mut tx_fifo, 16, true, need_vsync, false); // This is the sync pulse @@ -213,14 +220,28 @@ fn main() -> ! { } } -fn load_timing(fifo: &mut hal_pio::Tx<(pac::PIO0, hal_pio::SM0)>, period: u16, hsync: bool, vsync: bool, want_irq: bool) { +fn load_timing( + fifo: &mut hal_pio::Tx<(pac::PIO0, hal_pio::SM0)>, + period: u16, + hsync: bool, + vsync: bool, + want_irq: bool, +) { let command = if want_irq { // This command sets IRQ 0 - pio::InstructionOperands::IRQ { clear: false, wait: false, index: 0, relative: false } - } - else { + pio::InstructionOperands::IRQ { + clear: false, + wait: false, + index: 0, + relative: false, + } + } else { // This command is a no-op (it moves Y into Y) - pio::InstructionOperands::MOV { destination: pio::MovDestination::Y, op: pio::MovOperation::None, source: pio::MovSource::Y } + pio::InstructionOperands::MOV { + destination: pio::MovDestination::Y, + op: pio::MovOperation::None, + source: pio::MovSource::Y, + } }; let mut value: u32 = u32::from(command.encode()); if hsync { From dbfdc507e00611e1d1b58d9c3449a604eb5fc54e Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Sat, 30 Oct 2021 20:46:23 +0100 Subject: [PATCH 05/41] Switch back to 2018 edition. GHA ubuntu-latest only has 1.55 installed. --- Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index e6a9d55..d142fae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [package] authors = ["Jonathan 'theJPster' Pallant "] -edition = "2021" +edition = "2018" +resolver = "2" readme = "README.md" license = "GPL-3.0-or-later" name = "neotron-pico-bios" From 3ef70169a2b976c5068f1e44ff1dec488da26df8 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Sat, 30 Oct 2021 20:47:01 +0100 Subject: [PATCH 06/41] Double FIFO size. We don't need the RX fifo for the timing SM. --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 0981074..19b279f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -179,7 +179,7 @@ fn main() -> ! { let installed = pio.install(&program.program).unwrap(); let (mut sm, _rx_fifo, mut tx_fifo) = hal_pio::PIOBuilder::from_program(installed) - .buffers(hal_pio::Buffers::RxTx) + .buffers(hal_pio::Buffers::OnlyTx) .set_pins(0, 2) .out_pins(0, 2) .clock_divisor(5.0) From a29eec08c58b93c380c7e96d71c59d4653ffc569 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Sat, 30 Oct 2021 20:47:15 +0100 Subject: [PATCH 07/41] Clean ups. --- src/main.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index 19b279f..a347726 100644 --- a/src/main.rs +++ b/src/main.rs @@ -111,7 +111,7 @@ fn main() -> ! { // Create an object we can use to busy-wait for specified numbers of // milliseconds. For this to work, it needs to know our clock speed. - let mut delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().integer()); + let _delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().integer()); // sio is the *Single-cycle Input/Output* peripheral. It has all our GPIO // pins, as well as some mailboxes and other useful things for inter-core @@ -163,8 +163,6 @@ fn main() -> ! { let program = pio_proc::pio!( 32, " - ; Block until FIFO receives data - pull block .wrap_target ; Note autopull is set to 32-bits ; output is set to shift right From f319e62025c299ac19c3d7c084036c71b1ab54e9 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Sat, 30 Oct 2021 21:50:58 +0100 Subject: [PATCH 08/41] Add RGB data. Timing is wrong on RGB. Unsure why. --- src/main.rs | 96 +++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 71 insertions(+), 25 deletions(-) diff --git a/src/main.rs b/src/main.rs index a347726..f6074fa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -150,44 +150,82 @@ fn main() -> ! { // and it busy-waits the appropriate amount of time and trigger the // appropriate interrupts. - // Post + // Post where value: + // + // + // The SM will execute the instruction (you want to raise an IRQ at the + // start of the visible portion, to start another SM), set the H-Sync and + // V-Sync pins as desired, then wait the given number of clock cycles. // Need to configure PIO0SM1 to run the pixel loop. It waits for an IRQ - // (posted by the timing loop) then pulls pixel data from the FIFO. We - // post the number of pixels for that line, then the pixel data. + // (posted by the timing loop) then pulls pixel data from the FIFO. We post + // the number of pixels for that line, then the pixel data. - // Post ; each is a 12-bit value padded up to 16-bits. + // Post ... ; each maps to + // the RGB output pins. On a Neotron Pico, there are 12 (4 Red, 4 Green and + // 4 Blue) - so we set autopull to 12, and each value should be 12-bits long. - let (mut pio, sm0, _sm1, _sm2, _sm3) = pac.PIO0.split(&mut pac.RESETS); + let (mut pio, sm0, sm1, _sm2, _sm3) = pac.PIO0.split(&mut pac.RESETS); - let program = pio_proc::pio!( + let timing_program = pio_proc::pio!( 32, " .wrap_target - ; Note autopull is set to 32-bits - ; output is set to shift right - out exec, 16 ; Execute top 16-bits as an instruction - out pins, 2 ; Push bottom 2 bits to PINS - out x, 14 ; Push next 14 bits into X for the timing loop + ; Note autopull is set to 32-bits, OSR is set to shift right + ; Execute bottom 16-bits as an instruction + out exec, 16 + ; Push next 2 bits to PINS + out pins, 2 + ; Push last 14 bits into X for the timing loop + out x, 14 loop0: - jmp x-- loop0 ; Spin until X is zero + ; Spin until X is zero + jmp x-- loop0 .wrap " ); - let installed = pio.install(&program.program).unwrap(); - let (mut sm, _rx_fifo, mut tx_fifo) = hal_pio::PIOBuilder::from_program(installed) + let pixel_program = pio_proc::pio!( + 32, + " + .wrap_target + ; Wait for timing state machine to start visible line + wait 1 irq 7 + ; Read `num_pixels - 1` into X + out x, 12 + loop1: + ; Push out RGB pixels (NB: add a 3 clock wait, to make 5 clocks per pixel) + out pins, 12 + ; Repeat until all pixels sent + jmp x-- loop1 + .wrap + " + ); + + let timing_installed = pio.install(&timing_program.program).unwrap(); + let (mut timing_sm, _, mut timing_fifo) = hal_pio::PIOBuilder::from_program(timing_installed) .buffers(hal_pio::Buffers::OnlyTx) - .set_pins(0, 2) .out_pins(0, 2) .clock_divisor(5.0) .autopull(true) .out_shift_direction(hal_pio::ShiftDirection::Right) .pull_threshold(32) .build(sm0); - sm.set_pindirs([(0, hal_pio::PinDir::Output), (1, hal_pio::PinDir::Output)]); + timing_sm.set_pindirs([(0, hal_pio::PinDir::Output), (1, hal_pio::PinDir::Output)]); + + let pixels_installed = pio.install(&pixel_program.program).unwrap(); + let (mut pixel_sm, _, mut pixel_fifo) = hal_pio::PIOBuilder::from_program(pixels_installed) + .buffers(hal_pio::Buffers::OnlyTx) + .out_pins(2, 12) + .clock_divisor(1.0) + .autopull(true) + .out_shift_direction(hal_pio::ShiftDirection::Right) + .pull_threshold(12) + .build(sm1); + pixel_sm.set_pindirs((2..=14).map(|x| (x, hal_pio::PinDir::Output))); - sm.start(); + timing_sm.start(); + pixel_sm.start(); let mut x: u8 = 0; loop { @@ -204,16 +242,24 @@ fn main() -> ! { // 640 x 480, negative h-sync - four periods per video line let need_vsync = scanline >= 490 && scanline <= 491; - let _is_visible = scanline <= 480; + let is_visible = scanline <= 480; // This if the front porch - load_timing(&mut tx_fifo, 16, true, need_vsync, false); + load_timing(&mut timing_fifo, 16, true, need_vsync, false); // This is the sync pulse - load_timing(&mut tx_fifo, 96, false, need_vsync, false); + load_timing(&mut timing_fifo, 96, false, need_vsync, false); // This is the back porch - load_timing(&mut tx_fifo, 48, true, need_vsync, false); + load_timing(&mut timing_fifo, 48, true, need_vsync, false); // This is the visible portion - trigger the IRQ to start pixels moving - load_timing(&mut tx_fifo, 640, true, need_vsync, true); + load_timing(&mut timing_fifo, 640, true, need_vsync, true); + + // Load all the pixels for the line + if is_visible { + while !pixel_fifo.write(63) {} + for pixel in 1..=64 { + while !pixel_fifo.write(pixel) {} + } + } } } } @@ -223,14 +269,14 @@ fn load_timing( period: u16, hsync: bool, vsync: bool, - want_irq: bool, + raise_irq: bool, ) { - let command = if want_irq { + let command = if raise_irq { // This command sets IRQ 0 pio::InstructionOperands::IRQ { clear: false, wait: false, - index: 0, + index: 7, relative: false, } } else { From f441719b189d6a96497c7524a9b7e1b9f7f9459d Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Sat, 30 Oct 2021 21:52:42 +0100 Subject: [PATCH 09/41] Formatting. --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index f6074fa..f615315 100644 --- a/src/main.rs +++ b/src/main.rs @@ -200,7 +200,7 @@ fn main() -> ! { jmp x-- loop1 .wrap " - ); + ); let timing_installed = pio.install(&timing_program.program).unwrap(); let (mut timing_sm, _, mut timing_fifo) = hal_pio::PIOBuilder::from_program(timing_installed) From 0777718b911795933969dd86073121f8211aa65e Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Sat, 30 Oct 2021 22:36:19 +0100 Subject: [PATCH 10/41] Clear pixels at end of line. --- src/main.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main.rs b/src/main.rs index f615315..7261bb0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -191,13 +191,15 @@ fn main() -> ! { .wrap_target ; Wait for timing state machine to start visible line wait 1 irq 7 - ; Read `num_pixels - 1` into X + ; Read `num_pixels - 1` from OSR into Scratch Register X out x, 12 loop1: - ; Push out RGB pixels (NB: add a 3 clock wait, to make 5 clocks per pixel) - out pins, 12 + ; Push out one 12-bit RGB pixel (with a 3 clock wait, to make 5 clocks per pixel) + out pins 12 [3] ; Repeat until all pixels sent jmp x-- loop1 + ; Clear all pins after visible section + mov pins null .wrap " ); @@ -222,7 +224,7 @@ fn main() -> ! { .out_shift_direction(hal_pio::ShiftDirection::Right) .pull_threshold(12) .build(sm1); - pixel_sm.set_pindirs((2..=14).map(|x| (x, hal_pio::PinDir::Output))); + pixel_sm.set_pindirs((2..=13).map(|x| (x, hal_pio::PinDir::Output))); timing_sm.start(); pixel_sm.start(); @@ -257,7 +259,7 @@ fn main() -> ! { if is_visible { while !pixel_fifo.write(63) {} for pixel in 1..=64 { - while !pixel_fifo.write(pixel) {} + while !pixel_fifo.write(0xF00 + pixel) {} } } } From ac6f03b1ebc6566d7dac3ad4a1ddaba6245a752a Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Sat, 30 Oct 2021 22:43:06 +0100 Subject: [PATCH 11/41] Runs at 8MHz. Seem to be CPU limited filling the FIFO. Next stop, DMA! --- src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 7261bb0..2a1a5bd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -257,9 +257,9 @@ fn main() -> ! { // Load all the pixels for the line if is_visible { - while !pixel_fifo.write(63) {} + pixel_fifo.write(63); for pixel in 1..=64 { - while !pixel_fifo.write(0xF00 + pixel) {} + pixel_fifo.write(0xF00 + pixel); } } } From 342fbd469aba80068e5622bd0764a288a6cf0c18 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Sun, 31 Oct 2021 17:43:32 +0000 Subject: [PATCH 12/41] Set clock speed to 126 MHz. Divides down to 25.2 MHz which ~= 25.175 MHz in the VGA spec. --- src/main.rs | 42 +++++++++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/src/main.rs b/src/main.rs index 2a1a5bd..4db9de9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -94,19 +94,47 @@ fn main() -> ! { // Needed by the clock setup let mut watchdog = hal::watchdog::Watchdog::new(pac.WATCHDOG); - // Get ourselves up to a decent clock speed. - let clocks = hal::clocks::init_clocks_and_plls( - pico::XOSC_CRYSTAL_FREQ, - pac.XOSC, - pac.CLOCKS, + // Run at 126 MHz SYS_PLL, 48 MHz, USB_PLL + + let xosc = hal::xosc::setup_xosc_blocking(pac.XOSC, pico::XOSC_CRYSTAL_FREQ.Hz()) + .map_err(|_x| false) + .unwrap(); + + // Configure watchdog tick generation to tick over every microsecond + watchdog.enable_tick_generation((pico::XOSC_CRYSTAL_FREQ / 1_000_000) as u8); + + let mut clocks = hal::clocks::ClocksManager::new(pac.CLOCKS); + + let pll_sys = hal::pll::setup_pll_blocking( pac.PLL_SYS, + xosc.operating_frequency().into(), + hal::pll::PLLConfig { + vco_freq: Megahertz(1512), + refdiv: 1, + post_div1: 6, + post_div2: 2, + }, + &mut clocks, + &mut pac.RESETS, + ) + .map_err(|_x| false) + .unwrap(); + + let pll_usb = hal::pll::setup_pll_blocking( pac.PLL_USB, + xosc.operating_frequency().into(), + hal::pll::common_configs::PLL_USB_48MHZ, + &mut clocks, &mut pac.RESETS, - &mut watchdog, ) - .ok() + .map_err(|_x| false) .unwrap(); + clocks + .init_default(&xosc, &pll_sys, &pll_usb) + .map_err(|_x| false) + .unwrap(); + info!("Clocks OK"); // Create an object we can use to busy-wait for specified numbers of From 8864e6c13fd99e69818e2d0d604bc86b3a8e81ad Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Sun, 31 Oct 2021 17:46:59 +0000 Subject: [PATCH 13/41] The pixel SM clocks out pixels directly. The previous code suffered terribly from FIFO underflows. --- src/main.rs | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/src/main.rs b/src/main.rs index 4db9de9..d862033 100644 --- a/src/main.rs +++ b/src/main.rs @@ -205,10 +205,14 @@ fn main() -> ! { ; Push next 2 bits to PINS out pins, 2 ; Push last 14 bits into X for the timing loop + ; We want to wait five clock cycles per X out x, 14 + ; Two NOPS to make this section add up to five clocks + nop + nop loop0: ; Spin until X is zero - jmp x-- loop0 + jmp x-- loop0 [4] .wrap " ); @@ -218,13 +222,16 @@ fn main() -> ! { " .wrap_target ; Wait for timing state machine to start visible line - wait 1 irq 7 + wait 1 irq 0 ; Read `num_pixels - 1` from OSR into Scratch Register X - out x, 12 + set x 31 loop1: - ; Push out one 12-bit RGB pixel (with a 3 clock wait, to make 5 clocks per pixel) - out pins 12 [3] - ; Repeat until all pixels sent + ; Push out one 12-bit RGB pixel (with a 4 clock wait, to make 5 clocks per pixel) + set pins 15 [4] + set pins 0 [4] + set pins 15 [4] + set pins 0 [3] + ; Repeat until all blocks sent jmp x-- loop1 ; Clear all pins after visible section mov pins null @@ -236,7 +243,6 @@ fn main() -> ! { let (mut timing_sm, _, mut timing_fifo) = hal_pio::PIOBuilder::from_program(timing_installed) .buffers(hal_pio::Buffers::OnlyTx) .out_pins(0, 2) - .clock_divisor(5.0) .autopull(true) .out_shift_direction(hal_pio::ShiftDirection::Right) .pull_threshold(32) @@ -244,10 +250,10 @@ fn main() -> ! { timing_sm.set_pindirs([(0, hal_pio::PinDir::Output), (1, hal_pio::PinDir::Output)]); let pixels_installed = pio.install(&pixel_program.program).unwrap(); - let (mut pixel_sm, _, mut pixel_fifo) = hal_pio::PIOBuilder::from_program(pixels_installed) + let (mut pixel_sm, _, _pixel_fifo) = hal_pio::PIOBuilder::from_program(pixels_installed) .buffers(hal_pio::Buffers::OnlyTx) .out_pins(2, 12) - .clock_divisor(1.0) + .set_pins(2, 5) .autopull(true) .out_shift_direction(hal_pio::ShiftDirection::Right) .pull_threshold(12) @@ -281,15 +287,7 @@ fn main() -> ! { // This is the back porch load_timing(&mut timing_fifo, 48, true, need_vsync, false); // This is the visible portion - trigger the IRQ to start pixels moving - load_timing(&mut timing_fifo, 640, true, need_vsync, true); - - // Load all the pixels for the line - if is_visible { - pixel_fifo.write(63); - for pixel in 1..=64 { - pixel_fifo.write(0xF00 + pixel); - } - } + load_timing(&mut timing_fifo, 640, true, need_vsync, is_visible); } } } @@ -306,7 +304,7 @@ fn load_timing( pio::InstructionOperands::IRQ { clear: false, wait: false, - index: 7, + index: 0, relative: false, } } else { @@ -324,7 +322,7 @@ fn load_timing( if vsync { value |= 1 << 17; } - value |= (u32::from(period) - 4) << 18; + value |= (u32::from(period) - 2) << 18; while !fifo.write(value) { // Spin? } From 3a3b0e0414c16ac38991161d022644e45ccb0bbf Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Sun, 14 Nov 2021 19:02:17 +0000 Subject: [PATCH 14/41] Send out 640 pixels of blue stripes. --- src/main.rs | 41 ++++++++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/src/main.rs b/src/main.rs index d862033..7dca29e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -200,15 +200,15 @@ fn main() -> ! { " .wrap_target ; Note autopull is set to 32-bits, OSR is set to shift right - ; Execute bottom 16-bits as an instruction + + + ; Step 1. Execute bottom 16-bits of OSR as an instruction. This take two cycles. out exec, 16 - ; Push next 2 bits to PINS + ; Step 2. Push next 2 bits of OSR into `pins`, to set H-Sync and V-Sync out pins, 2 - ; Push last 14 bits into X for the timing loop - ; We want to wait five clock cycles per X + ; Step 3. Push last 14 bits of OSR into X for the timing loop. out x, 14 - ; Two NOPS to make this section add up to five clocks - nop + ; Step 4. Make this section add up to exactly five clocks. nop loop0: ; Spin until X is zero @@ -223,15 +223,19 @@ fn main() -> ! { .wrap_target ; Wait for timing state machine to start visible line wait 1 irq 0 - ; Read `num_pixels - 1` from OSR into Scratch Register X - set x 31 + ; Transmit 32 stripes (and pad to a whole pixel) + set x, 31 + nop + nop + nop loop1: - ; Push out one 12-bit RGB pixel (with a 4 clock wait, to make 5 clocks per pixel) - set pins 15 [4] - set pins 0 [4] - set pins 15 [4] - set pins 0 [3] - ; Repeat until all blocks sent + ; Each stripe has 20 pixels. Each pixel is 5 clock cycles + ; (converts 126 MHz to 25.2 MHz) + set pins 15 [24] ; 5 pixels (minus one for the instruction) + set pins 7 [24] ; 5 pixels (...) + set pins 15 [24] ; 5 pixels (...) + set pins 7 [23] ; 5 pixels (minus two, for the inst. and the jump) + ; Repeat until all stripes sent jmp x-- loop1 ; Clear all pins after visible section mov pins null @@ -249,11 +253,18 @@ fn main() -> ! { .build(sm0); timing_sm.set_pindirs([(0, hal_pio::PinDir::Output), (1, hal_pio::PinDir::Output)]); + // Important notes! + // + // You must not set a clock_divider (other than 1.0) on the pixel state + // machine. You might want the pixels to be twice as wide (or mode), but + // enabling a clock divider adds a lot of jitter (i.e. the start each + // each line differs by some number of 126 MHz clock cycles). + let pixels_installed = pio.install(&pixel_program.program).unwrap(); let (mut pixel_sm, _, _pixel_fifo) = hal_pio::PIOBuilder::from_program(pixels_installed) .buffers(hal_pio::Buffers::OnlyTx) .out_pins(2, 12) - .set_pins(2, 5) + .set_pins(10, 4) .autopull(true) .out_shift_direction(hal_pio::ShiftDirection::Right) .pull_threshold(12) From 6068e6a9dda3f91769d1e64b2f4fd83e5fa6ae1b Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Tue, 16 Nov 2021 16:59:55 +0000 Subject: [PATCH 15/41] Switching to use DMA to load Timing and Pixel FIFO. Currently the DMA IRQ doesn't seem to fire. --- Cargo.toml | 6 +- src/main.rs | 516 ++++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 420 insertions(+), 102 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d142fae..f003ed6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,8 @@ version = "0.2.0" # Useful Cortex-M specific functions (e.g. SysTick) cortex-m = "0.7" # The Raspberry Pi Pico HAL -pico = { git = "https://github.com/rp-rs/rp-hal.git" } +# pico = { git = "https://github.com/rp-rs/rp-hal.git" } +pico = { path = "/home/jonathan/Documents/rp-rs/rp-hal/boards/pico" } # Cortex-M run-time (or start-up) code cortex-m-rt = "0.7" # The BIOS API we export to the OS @@ -63,3 +64,6 @@ codegen-units = 1 debug = true # better optimizations lto = true + +[patch.crates-io] +rp2040-pac = { path = "/home/jonathan/Documents/rp-rs/rp2040-pac" } diff --git a/src/main.rs b/src/main.rs index 7dca29e..2e747a1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -37,10 +37,10 @@ // Imports // ----------------------------------------------------------------------------- +use core::sync::atomic::{AtomicBool, AtomicI16, AtomicU16, Ordering}; use cortex_m_rt::entry; use defmt::*; use defmt_rtt as _; -use embedded_hal::digital::v2::OutputPin; use embedded_time::rate::*; use git_version::git_version; use hal::clocks::Clock; @@ -48,11 +48,34 @@ use panic_probe as _; use pico::{ self, hal::{ - self, pac, + self, + pac::{self, interrupt}, pio::{self as hal_pio, PIOExt}, }, }; +// ----------------------------------------------------------------------------- +// Types +// ----------------------------------------------------------------------------- + +#[repr(C, align(16))] +struct ScanlineTimingBuffer { + data: [u32; 4], +} + +#[repr(C, align(16))] +struct TimingBuffer { + visible_line: ScanlineTimingBuffer, + vblank_porch_buffer: ScanlineTimingBuffer, + vblank_sync_buffer: ScanlineTimingBuffer, +} + +#[repr(C, align(16))] +struct LineBuffer { + length: u32, + pixels: [u32; 320], +} + // ----------------------------------------------------------------------------- // Static and Const Data // ----------------------------------------------------------------------------- @@ -70,11 +93,84 @@ pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER; /// BIOS version const GIT_VERSION: &str = git_version!(); -// ----------------------------------------------------------------------------- -// Types -// ----------------------------------------------------------------------------- +/// Stores our timing data which we DMA into the timing PIO State Machine +static TIMING_BUFFER: TimingBuffer = TimingBuffer { + // Note - the order of the arguments to `make_timing` is: + // + // * H-Sync Pulse (true = high, false = low) + // * V-Sync Pulse (true = high, false = low) + // * Generate pixel start IRQ (true or false) + visible_line: ScanlineTimingBuffer { + data: [ + // Front porch (as per the spec) + make_timing(16 * 5, true, false, false), + // Sync pulse (as per the spec) + make_timing(96 * 5, false, false, false), + // Back porch (shortened by two pixels because the + // video starts two pixels late) + make_timing(48 * 5, true, false, false), + // Visible portion. It also triggers the IRQ to start pixels moving. + make_timing(640 * 5, true, false, true), + ], + }, + vblank_porch_buffer: ScanlineTimingBuffer { + data: [ + // Front porch + make_timing(16 * 5, true, false, false), + // Sync pulse + make_timing(96 * 5, false, false, false), + // Back porch + make_timing(48 * 5, true, false, false), + // 'visible' portion (but it's blank) + make_timing(640 * 5, true, false, false), + ], + }, + vblank_sync_buffer: ScanlineTimingBuffer { + data: [ + // Front porch + make_timing(16 * 5, true, true, false), + // Sync pulse + make_timing(96 * 5, false, true, false), + // Back porch + make_timing(48 * 5, true, true, false), + // Visible portion (but it's blank) + make_timing(640 * 5, true, true, false), + ], + }, +}; + +/// Tracks which scanline we are currently on (for timing purposes => it goes 0..=524) +static CURRENT_TIMING_LINE: AtomicU16 = AtomicU16::new(0); + +/// Tracks which scanline we are drawing (for pixel purposes => it goes -3..=479) +static CURRENT_DISPLAY_LINE: AtomicI16 = AtomicI16::new(-3); + +/// Set to true when we are at the top of the frame (and in the v-blank) +static NEW_FRAME: AtomicBool = AtomicBool::new(false); -// None +/// Somewhere to stash the DMA controller object, so the IRQ can find it +static mut DMA_PERIPH: Option = None; + +/// DMA channel for the timing FIFO +static mut TIMING_DMA_CHAN: usize = 0; + +/// DMA channel for the pixel FIFO +static mut PIXEL_DMA_CHAN: usize = 1; + +/// A dummy value for the DMA to read whilst we wait for the visible portion to start +static mut PIXEL_DATA_ZERO_BUFFER: [u32; 1] = [0xFFFF_FFFF]; + +/// 12-bit pixels for the even scan-lines (0, 2, 4 ... 478). Defaults to black. +static mut PIXEL_DATA_BUFFER_EVEN: LineBuffer = LineBuffer { + length: 320, + pixels: [0; 320], +}; + +/// 12-bit pixels for the odd scan-lines (1, 3, 5 ... 479). Defaults to white. +static mut PIXEL_DATA_BUFFER_ODD: LineBuffer = LineBuffer { + length: 320, + pixels: [0x0FFF_0FFF; 320], +}; // ----------------------------------------------------------------------------- // Functions @@ -156,8 +252,6 @@ fn main() -> ! { info!("Pins OK"); - // Grab the LED pin - let mut led_pin = pins.led.into_push_pull_output(); // Give H-Sync, V-Sync and 12 RGB colour pins to PIO0 to output video let _h_sync = pins.gpio0.into_mode::(); let _v_sync = pins.gpio1.into_mode::(); @@ -174,68 +268,66 @@ fn main() -> ! { let _blue2 = pins.gpio12.into_mode::(); let _blue3 = pins.gpio13.into_mode::(); - // Need to configure PIO0SM0 to run the timing loop. We post timing data - // and it busy-waits the appropriate amount of time and trigger the - // appropriate interrupts. + // Grab PIO0 and the state machines it contains + let (mut pio, sm0, sm1, _sm2, _sm3) = pac.PIO0.split(&mut pac.RESETS); + // This program runs the timing loop. We post timing data (i.e. the length + // of each period, along with what the H-Sync and V-Sync pins should do) + // and it sets the GPIO pins and busy-waits the appropriate amount of + // time. It also takes an extra 'instruction' which we can use to trigger + // the appropriate interrupts. + // // Post where value: // // - // The SM will execute the instruction (you want to raise an IRQ at the - // start of the visible portion, to start another SM), set the H-Sync and - // V-Sync pins as desired, then wait the given number of clock cycles. - - // Need to configure PIO0SM1 to run the pixel loop. It waits for an IRQ - // (posted by the timing loop) then pulls pixel data from the FIFO. We post - // the number of pixels for that line, then the pixel data. - - // Post ... ; each maps to - // the RGB output pins. On a Neotron Pico, there are 12 (4 Red, 4 Green and - // 4 Blue) - so we set autopull to 12, and each value should be 12-bits long. - - let (mut pio, sm0, sm1, _sm2, _sm3) = pac.PIO0.split(&mut pac.RESETS); - + // The SM will execute the instruction (typically either a NOP or an IRQ), + // set the H-Sync and V-Sync pins as desired, then wait the given number + // of clock cycles. + // + // Note: autopull should be set to 32-bits, OSR is set to shift right. let timing_program = pio_proc::pio!( 32, " .wrap_target - ; Note autopull is set to 32-bits, OSR is set to shift right - - - ; Step 1. Execute bottom 16-bits of OSR as an instruction. This take two cycles. - out exec, 16 - ; Step 2. Push next 2 bits of OSR into `pins`, to set H-Sync and V-Sync + ; Step 1. Push next 2 bits of OSR into `pins`, to set H-Sync and V-Sync out pins, 2 - ; Step 3. Push last 14 bits of OSR into X for the timing loop. + ; Step 2. Push last 14 bits of OSR into X for the timing loop. out x, 14 - ; Step 4. Make this section add up to exactly five clocks. - nop + ; Step 3. Execute bottom 16-bits of OSR as an instruction. This take two cycles. + out exec, 16 loop0: ; Spin until X is zero - jmp x-- loop0 [4] + jmp x-- loop0 .wrap " ); + // This is the video pixels program. It waits for an IRQ + // (posted by the timing loop) then pulls pixel data from the FIFO. We post + // the number of pixels for that line, then the pixel data. + // + // Post ... ; each maps to + // the RGB output pins. On a Neotron Pico, there are 12 (4 Red, 4 Green and + // 4 Blue) - so we set autopull to 12, and each value should be 12-bits long. + // + // Currently the FIFO support is disabled and it just puts out 640 fixed + // 12-bit pixels per scanline. + // + // Note autopull should be set to 12-bits, OSR is set to shift right. let pixel_program = pio_proc::pio!( 32, " .wrap_target ; Wait for timing state machine to start visible line wait 1 irq 0 - ; Transmit 32 stripes (and pad to a whole pixel) - set x, 31 - nop - nop - nop + ; Read the line length (in pixel-pairs) + out x, 32 loop1: - ; Each stripe has 20 pixels. Each pixel is 5 clock cycles - ; (converts 126 MHz to 25.2 MHz) - set pins 15 [24] ; 5 pixels (minus one for the instruction) - set pins 7 [24] ; 5 pixels (...) - set pins 15 [24] ; 5 pixels (...) - set pins 7 [23] ; 5 pixels (minus two, for the inst. and the jump) - ; Repeat until all stripes sent + ; Write out first pixel - takes 5 clocks + out pins, 16 [4] + ; Write out second pixel - takes 5 clocks (allowing one for the jump) + out pins, 16 [3] + ; Repeat until all pixel pairs sent jmp x-- loop1 ; Clear all pins after visible section mov pins null @@ -243,8 +335,33 @@ fn main() -> ! { " ); + // These two state machines run thus: + // + // | Clock | Timing PIOSM | Pixel PIOSM | + // |:------|:-------------|:-----------------| + // | 1 | out pins, 2 | wait 1 irq 0 | + // | 2 | out x, 14 | wait 1 irq 0 | + // | 3 | out exec, 16 | wait 1 irq 0 | + // | 4 | | wait 1 irq 0 | + // | 5 | jmp x-- | wait 1 irq 0 | + // | 6 | | out x, 12 | + // | 7 | | out pins, 16 [4] | + // | 8 | | .. | + // | 9 | | .. | + // | 10 | | .. | + // | 11 | | .. | + // | 12 | | out pins, 16 [3] | + // | 13 | | .. | + // | 14 | | .. | + // | 15 | | .. | + // | 16 | | jump x-- loop1 | + // + // Note: Credit to + // https://gregchadwick.co.uk/blog/playing-with-the-pico-pt5/ who had a + // very similar idea to me, but wrote it up far better than I ever could. + let timing_installed = pio.install(&timing_program.program).unwrap(); - let (mut timing_sm, _, mut timing_fifo) = hal_pio::PIOBuilder::from_program(timing_installed) + let (mut timing_sm, _, timing_fifo) = hal_pio::PIOBuilder::from_program(timing_installed) .buffers(hal_pio::Buffers::OnlyTx) .out_pins(0, 2) .autopull(true) @@ -261,81 +378,278 @@ fn main() -> ! { // each line differs by some number of 126 MHz clock cycles). let pixels_installed = pio.install(&pixel_program.program).unwrap(); - let (mut pixel_sm, _, _pixel_fifo) = hal_pio::PIOBuilder::from_program(pixels_installed) + let (mut pixel_sm, _, pixel_fifo) = hal_pio::PIOBuilder::from_program(pixels_installed) .buffers(hal_pio::Buffers::OnlyTx) .out_pins(2, 12) - .set_pins(10, 4) .autopull(true) .out_shift_direction(hal_pio::ShiftDirection::Right) - .pull_threshold(12) + .pull_threshold(32) .build(sm1); pixel_sm.set_pindirs((2..=13).map(|x| (x, hal_pio::PinDir::Output))); + unsafe { + // dma_channel_config timing_dma_chan_config = dma_channel_get_default_config(timing_dma_chan); + + // Transfer 32 bits at a time + // channel_config_set_transfer_data_size(&timing_dma_chan_config, DMA_SIZE_32); + // Increment read to go through the sync timing command buffer + // channel_config_set_read_increment(&timing_dma_chan_config, true); + // Don't increment write so we always transfer to the PIO FIFO + // channel_config_set_write_increment(&timing_dma_chan_config, false); + // Transfer when there's space in the sync SM FIFO + // channel_config_set_dreq(&timing_dma_chan_config, pio_get_dreq(pio, sync_sm, true)); + pac.DMA.ch[TIMING_DMA_CHAN].ch_ctrl_trig.modify(|_r, w| { + w.data_size().size_word(); + w.incr_read().set_bit(); + w.incr_write().clear_bit(); + w.treq_sel().bits(timing_fifo.dreq_value()); + w.chain_to().bits(TIMING_DMA_CHAN as u8); + w.ring_size().bits(0); + w.ring_sel().clear_bit(); + w.bswap().clear_bit(); + w.irq_quiet().clear_bit(); + w.en().set_bit(); + w.sniff_en().clear_bit(); + w + }); + + // Setup the channel and set it going + // dma_channel_configure( + // timing_dma_chan, + // &timing_dma_chan_config, + // &pio->txf[sync_sm], // Write to PIO TX FIFO + // vblank_sync_buffer, // Begin with vblank sync line + // 4, // 4 command words in each sync buffer + // false // Don't start yet + // ); + // set_read_addr + pac.DMA.ch[TIMING_DMA_CHAN] + .ch_read_addr + .write(|w| w.bits(TIMING_BUFFER.vblank_sync_buffer.data.as_ptr() as usize as u32)); + // set_write_addr + pac.DMA.ch[TIMING_DMA_CHAN] + .ch_write_addr + .write(|w| w.bits(timing_fifo.dma_address())); + // set_trans_count + pac.DMA.ch[TIMING_DMA_CHAN] + .ch_trans_count + .write(|w| w.bits(4)); + + // Transfer 32 bits at a time + // channel_config_set_transfer_data_size(&pixel_dma_chan_config, DMA_SIZE_32); + // Increment read to go through the pixel data buffer + // channel_config_set_read_increment(&pixel_dma_chan_config, false); + // Don't increment write so we always transfer to the PIO FIFO + // channel_config_set_write_increment(&pixel_dma_chan_config, false); + // Transfer when there's space in the pixel SM FIFO + // channel_config_set_dreq(&pixel_dma_chan_config, pio_get_dreq(pio, line_sm, true)); + pac.DMA.ch[PIXEL_DMA_CHAN].ch_ctrl_trig.modify(|_r, w| { + w.data_size().size_word(); + w.incr_read().clear_bit(); + w.incr_write().clear_bit(); + w.treq_sel().bits(pixel_fifo.dreq_value()); + w.chain_to().bits(PIXEL_DMA_CHAN as u8); + w.ring_size().bits(0); + w.ring_sel().clear_bit(); + w.bswap().clear_bit(); + w.irq_quiet().clear_bit(); + w.en().set_bit(); + w.sniff_en().clear_bit(); + w + }); + // Setup the channel and set it going + // dma_channel_configure( + // pixel_dma_chan, + // &pixel_dma_chan_config, + // &pio->txf[line_sm], // Write to PIO TX FIFO + // &line_data_zero_buffer, // First line output will be white line + // 160, // Transfer complete contents of `line_data_buffer` + // false // Don't start yet + // ); + pac.DMA.ch[PIXEL_DMA_CHAN] + .ch_read_addr + .write(|w| w.bits(PIXEL_DATA_ZERO_BUFFER.as_ptr() as usize as u32)); + // set_write_addr + pac.DMA.ch[PIXEL_DMA_CHAN] + .ch_write_addr + .write(|w| w.bits(pixel_fifo.dma_address())); + // set_trans_count + pac.DMA.ch[PIXEL_DMA_CHAN] + .ch_trans_count + .write(|w| w.bits(321)); + // Setup interrupt handler for line and sync DMA channels + //dma_channel_set_irq0_enabled(pixel_dma_chan, true); + //dma_channel_set_irq0_enabled(timing_dma_chan, true); + pac.DMA.inte0.modify(|_r, w| { + w.inte0() + .bits((1 << PIXEL_DMA_CHAN) | (1 << TIMING_DMA_CHAN)) + }); + //irq_set_enabled(DMA_IRQ_0, true); + pac::NVIC::unpend(pac::Interrupt::DMA_IRQ_0); + pac::NVIC::unmask(pac::Interrupt::DMA_IRQ_0); + + pac.DMA + .multi_chan_trigger + .write(|w| w.bits(1 << PIXEL_DMA_CHAN)); + pac.DMA + .multi_chan_trigger + .write(|w| w.bits(1 << TIMING_DMA_CHAN)); + + DMA_PERIPH = Some(pac.DMA); + } + + info!("DMA set-up complete"); + timing_sm.start(); pixel_sm.start(); - let mut x: u8 = 0; + info!("State Machines running"); + loop { - // Flash a pin every 60 frames, or once a second - x = x.wrapping_add(1); - if x == 60 { - led_pin.set_high().unwrap(); - x = 0; - } else { - led_pin.set_low().unwrap(); - } - // This should all be moved off into an IRQ so it doesn't block the main thread - for scanline in 1..=525 { - // 640 x 480, negative h-sync - four periods per video line - - let need_vsync = scanline >= 490 && scanline <= 491; - let is_visible = scanline <= 480; - - // This if the front porch - load_timing(&mut timing_fifo, 16, true, need_vsync, false); - // This is the sync pulse - load_timing(&mut timing_fifo, 96, false, need_vsync, false); - // This is the back porch - load_timing(&mut timing_fifo, 48, true, need_vsync, false); - // This is the visible portion - trigger the IRQ to start pixels moving - load_timing(&mut timing_fifo, 640, true, need_vsync, is_visible); - } + cortex_m::asm::wfi(); + info!("Line is {}/480, {}/525", CURRENT_DISPLAY_LINE.load(Ordering::Relaxed), CURRENT_TIMING_LINE.load(Ordering::Relaxed)); } } -fn load_timing( - fifo: &mut hal_pio::Tx<(pac::PIO0, hal_pio::SM0)>, - period: u16, - hsync: bool, - vsync: bool, - raise_irq: bool, -) { +const fn make_timing(period: u32, hsync: bool, vsync: bool, raise_irq: bool) -> u32 { let command = if raise_irq { - // This command sets IRQ 0 - pio::InstructionOperands::IRQ { - clear: false, - wait: false, - index: 0, - relative: false, - } + // This command sets IRQ 0. It is the same as: + // + // ``` + // pio::InstructionOperands::IRQ { + // clear: false, + // wait: false, + // index: 0, + // relative: false, + // }.encode() + // ``` + // + // Unfortunately encoding this isn't a const-fn, so we cheat: + 0xc000 } else { // This command is a no-op (it moves Y into Y) - pio::InstructionOperands::MOV { - destination: pio::MovDestination::Y, - op: pio::MovOperation::None, - source: pio::MovSource::Y, - } + // + // ``` + // pio::InstructionOperands::MOV { + // destination: pio::MovDestination::Y, + // op: pio::MovOperation::None, + // source: pio::MovSource::Y, + // }.encode() + // ``` + // + // Unfortunately encoding this isn't a const-fn, so we cheat: + 0xa042 }; - let mut value: u32 = u32::from(command.encode()); + let mut value: u32 = 0; if hsync { - value |= 1 << 16; + value |= 1 << 0; } if vsync { - value |= 1 << 17; + value |= 1 << 1; + } + value |= (period - 6) << 2; + value | command << 16 +} + +#[interrupt] +unsafe fn DMA_IRQ_0() { + let dma: &mut pac::DMA = DMA_PERIPH.as_mut().unwrap(); + let status = dma.ints0.read().bits(); + + info!("IRQ!"); + + // Check if this is a DMA interrupt for the sync DMA channel + let timing_dma_chan_irq = (status & (1 << TIMING_DMA_CHAN)) != 0; + + // Check if this is a DMA interrupt for the line DMA channel + let pixel_dma_chan_irq = (status & (1 << PIXEL_DMA_CHAN)) != 0; + + if timing_dma_chan_irq { + // clear timing_dma_chan bit in DMA interrupt bitfield + dma.ints0.write(|w| w.bits(1 << TIMING_DMA_CHAN)); + + let old_timing_line = CURRENT_TIMING_LINE.load(Ordering::Relaxed); + let timing_line = if old_timing_line < 524 { + old_timing_line + 1 + } else { + 0 + }; + CURRENT_TIMING_LINE.store(timing_line, Ordering::Relaxed); + + info!("Sync {}", timing_line); + + let buffer = if (timing_line == 0) || (timing_line == 1) { + // V-Sync pulse + &TIMING_BUFFER.vblank_sync_buffer + } else if timing_line < 32 { + // VGA back porch following VGA sync pulse + &TIMING_BUFFER.vblank_porch_buffer + } else if timing_line < 515 { + // Visible lines (including three lines of the back porch we treat + // as visible to get ourselves up and running) + &TIMING_BUFFER.visible_line + } else { + // Front porch + &TIMING_BUFFER.vblank_porch_buffer + }; + dma.ch[TIMING_DMA_CHAN] + .ch_al3_read_addr_trig + .write(|w| w.bits(buffer as *const _ as usize as u32)) + } + + if pixel_dma_chan_irq { + dma.ints0.write(|w| w.bits(1 << TIMING_DMA_CHAN)); + + let display_line = CURRENT_DISPLAY_LINE.load(Ordering::Relaxed); + + info!("Line {}", display_line); + + if display_line == 479 { + // Set ourselves three lines early + CURRENT_DISPLAY_LINE.store(-3, Ordering::Relaxed); + NEW_FRAME.store(true, Ordering::Relaxed); + // Setup Line DMA channel to read zero lines for dummy lines and set it going. + // Disable read increment so just read zero over and over for dummy lines. + // DMA won't actually begin until line PIO starts consuming it in the next + // frame. + dma.ch[PIXEL_DMA_CHAN] + .ch_ctrl_trig + .modify(|_r, w| w.incr_read().clear_bit()); + dma.ch[PIXEL_DMA_CHAN] + .ch_al3_read_addr_trig + .write(|w| w.bits(PIXEL_DATA_ZERO_BUFFER.as_ptr() as usize as u32)) + } else { + let display_line = display_line + 1; + CURRENT_DISPLAY_LINE.store(display_line, Ordering::Relaxed); + + if display_line == 0 { + // Our first line - turn on read address increment on the DMA + dma.ch[PIXEL_DMA_CHAN] + .ch_ctrl_trig + .modify(|_r, w| w.incr_read().set_bit()); + } + + if display_line < 0 { + dma.ch[PIXEL_DMA_CHAN] + .ch_al3_read_addr_trig + .write(|w| w.bits(PIXEL_DATA_ZERO_BUFFER.as_ptr() as usize as u32)) + } else if (display_line & 1) == 1 { + dma.ch[PIXEL_DMA_CHAN] + .ch_al3_read_addr_trig + .write(|w| w.bits(PIXEL_DATA_BUFFER_ODD.as_ptr())) + } else { + dma.ch[PIXEL_DMA_CHAN] + .ch_al3_read_addr_trig + .write(|w| w.bits(PIXEL_DATA_BUFFER_EVEN.as_ptr())) + } + } } - value |= (u32::from(period) - 2) << 18; - while !fifo.write(value) { - // Spin? +} + +impl LineBuffer { + /// Convert the line buffer to a 32-bit address that the DMA engine understands. + fn as_ptr(&self) -> u32 { + self as *const _ as usize as u32 } } From 04113e751485b2b83a3b88c6b42a64d17238365a Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Tue, 16 Nov 2021 17:21:14 +0000 Subject: [PATCH 16/41] Take DMA out of reset, and don't enable IRQ until static var is set-up. --- src/main.rs | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/main.rs b/src/main.rs index 2e747a1..399d9a8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -388,6 +388,9 @@ fn main() -> ! { pixel_sm.set_pindirs((2..=13).map(|x| (x, hal_pio::PinDir::Output))); unsafe { + pac.RESETS.reset.modify(|_, w| w.dma().clear_bit()); + while pac.RESETS.reset_done.read().dma().bit_is_clear() {} + // dma_channel_config timing_dma_chan_config = dma_channel_get_default_config(timing_dma_chan); // Transfer 32 bits at a time @@ -484,9 +487,6 @@ fn main() -> ! { w.inte0() .bits((1 << PIXEL_DMA_CHAN) | (1 << TIMING_DMA_CHAN)) }); - //irq_set_enabled(DMA_IRQ_0, true); - pac::NVIC::unpend(pac::Interrupt::DMA_IRQ_0); - pac::NVIC::unmask(pac::Interrupt::DMA_IRQ_0); pac.DMA .multi_chan_trigger @@ -495,7 +495,15 @@ fn main() -> ! { .multi_chan_trigger .write(|w| w.bits(1 << TIMING_DMA_CHAN)); + info!("DMA enabled"); + DMA_PERIPH = Some(pac.DMA); + + //irq_set_enabled(DMA_IRQ_0, true); + pac::NVIC::unpend(pac::Interrupt::DMA_IRQ_0); + pac::NVIC::unmask(pac::Interrupt::DMA_IRQ_0); + + info!("IRQs enabled"); } info!("DMA set-up complete"); @@ -553,11 +561,12 @@ const fn make_timing(period: u32, hsync: bool, vsync: bool, raise_irq: bool) -> #[interrupt] unsafe fn DMA_IRQ_0() { - let dma: &mut pac::DMA = DMA_PERIPH.as_mut().unwrap(); + let dma: &mut pac::DMA = match DMA_PERIPH.as_mut() { + Some(dma) => { dma } + None => { return; } + }; let status = dma.ints0.read().bits(); - info!("IRQ!"); - // Check if this is a DMA interrupt for the sync DMA channel let timing_dma_chan_irq = (status & (1 << TIMING_DMA_CHAN)) != 0; From db80725a6a9bec57c9badcada7819aad11be5507 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Tue, 16 Nov 2021 17:23:12 +0000 Subject: [PATCH 17/41] Resetting the wrong DMA interrupt. --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 399d9a8..80af4cb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -607,7 +607,7 @@ unsafe fn DMA_IRQ_0() { } if pixel_dma_chan_irq { - dma.ints0.write(|w| w.bits(1 << TIMING_DMA_CHAN)); + dma.ints0.write(|w| w.bits(1 << PIXEL_DMA_CHAN)); let display_line = CURRENT_DISPLAY_LINE.load(Ordering::Relaxed); From b4100187dfb374712259bc32d308a8b6035f46c7 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Tue, 16 Nov 2021 17:23:44 +0000 Subject: [PATCH 18/41] Formatting. --- src/main.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/main.rs b/src/main.rs index 80af4cb..b6cdad8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -389,7 +389,7 @@ fn main() -> ! { unsafe { pac.RESETS.reset.modify(|_, w| w.dma().clear_bit()); - while pac.RESETS.reset_done.read().dma().bit_is_clear() {} + while pac.RESETS.reset_done.read().dma().bit_is_clear() {} // dma_channel_config timing_dma_chan_config = dma_channel_get_default_config(timing_dma_chan); @@ -515,7 +515,11 @@ fn main() -> ! { loop { cortex_m::asm::wfi(); - info!("Line is {}/480, {}/525", CURRENT_DISPLAY_LINE.load(Ordering::Relaxed), CURRENT_TIMING_LINE.load(Ordering::Relaxed)); + info!( + "Line is {}/480, {}/525", + CURRENT_DISPLAY_LINE.load(Ordering::Relaxed), + CURRENT_TIMING_LINE.load(Ordering::Relaxed) + ); } } @@ -562,8 +566,10 @@ const fn make_timing(period: u32, hsync: bool, vsync: bool, raise_irq: bool) -> #[interrupt] unsafe fn DMA_IRQ_0() { let dma: &mut pac::DMA = match DMA_PERIPH.as_mut() { - Some(dma) => { dma } - None => { return; } + Some(dma) => dma, + None => { + return; + } }; let status = dma.ints0.read().bits(); From 7f3fb4517b4fa27c4996e846d4a02c83130b4904 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Tue, 16 Nov 2021 18:02:12 +0000 Subject: [PATCH 19/41] Can generate a checker-board. Pulling the length from the FIFO doesn't seem to work, so the size is fixed at 32 pixels across. The delays are increased to make the pixels wider too, and it spends more time on each line before switching. --- src/main.rs | 78 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 47 insertions(+), 31 deletions(-) diff --git a/src/main.rs b/src/main.rs index b6cdad8..f017f18 100644 --- a/src/main.rs +++ b/src/main.rs @@ -70,10 +70,11 @@ struct TimingBuffer { vblank_sync_buffer: ScanlineTimingBuffer, } +const NUM_PIXEL_PAIRS: usize = 32; + #[repr(C, align(16))] struct LineBuffer { - length: u32, - pixels: [u32; 320], + pixels: [u32; NUM_PIXEL_PAIRS + 1], } // ----------------------------------------------------------------------------- @@ -162,14 +163,16 @@ static mut PIXEL_DATA_ZERO_BUFFER: [u32; 1] = [0xFFFF_FFFF]; /// 12-bit pixels for the even scan-lines (0, 2, 4 ... 478). Defaults to black. static mut PIXEL_DATA_BUFFER_EVEN: LineBuffer = LineBuffer { - length: 320, - pixels: [0; 320], + // The length is one less than the number of pixel pairs because of the + // way the jmp works in the PIO program (it's a do-while loop). + pixels: [0; NUM_PIXEL_PAIRS + 1], }; /// 12-bit pixels for the odd scan-lines (1, 3, 5 ... 479). Defaults to white. static mut PIXEL_DATA_BUFFER_ODD: LineBuffer = LineBuffer { - length: 320, - pixels: [0x0FFF_0FFF; 320], + // The length is one less than the number of pixel pairs because of the + // way the jmp works in the PIO program (it's a do-while loop). + pixels: [0; NUM_PIXEL_PAIRS + 1], }; // ----------------------------------------------------------------------------- @@ -187,6 +190,18 @@ fn main() -> ! { // Grab the singleton containing all the generic Cortex-M peripherals let core = pac::CorePeripherals::take().unwrap(); + unsafe { + PIXEL_DATA_BUFFER_EVEN.pixels[0] = NUM_PIXEL_PAIRS as u32 - 1; + for px in PIXEL_DATA_BUFFER_EVEN.pixels[1..].iter_mut() { + *px = 0x0FFF_0000; + } + + PIXEL_DATA_BUFFER_ODD.pixels[0] = NUM_PIXEL_PAIRS as u32 - 1; + for px in PIXEL_DATA_BUFFER_ODD.pixels[1..].iter_mut() { + *px = 0x0000_0FFF; + } + } + // Needed by the clock setup let mut watchdog = hal::watchdog::Watchdog::new(pac.WATCHDOG); @@ -321,12 +336,13 @@ fn main() -> ! { ; Wait for timing state machine to start visible line wait 1 irq 0 ; Read the line length (in pixel-pairs) - out x, 32 + ; out x, 32 + set x, 31 loop1: - ; Write out first pixel - takes 5 clocks - out pins, 16 [4] - ; Write out second pixel - takes 5 clocks (allowing one for the jump) - out pins, 16 [3] + ; Write out first pixel - takes 5 clocks per pixel (4 pixels) + out pins, 16 [19] + ; Write out second pixel - takes 5 clocks per pixel (4 pixels, allowing one for the jump) + out pins, 16 [18] ; Repeat until all pixel pairs sent jmp x-- loop1 ; Clear all pins after visible section @@ -479,7 +495,8 @@ fn main() -> ! { // set_trans_count pac.DMA.ch[PIXEL_DMA_CHAN] .ch_trans_count - .write(|w| w.bits(321)); + .write(|w| w.bits(NUM_PIXEL_PAIRS as u32)); + // Setup interrupt handler for line and sync DMA channels //dma_channel_set_irq0_enabled(pixel_dma_chan, true); //dma_channel_set_irq0_enabled(timing_dma_chan, true); @@ -488,15 +505,6 @@ fn main() -> ! { .bits((1 << PIXEL_DMA_CHAN) | (1 << TIMING_DMA_CHAN)) }); - pac.DMA - .multi_chan_trigger - .write(|w| w.bits(1 << PIXEL_DMA_CHAN)); - pac.DMA - .multi_chan_trigger - .write(|w| w.bits(1 << TIMING_DMA_CHAN)); - - info!("DMA enabled"); - DMA_PERIPH = Some(pac.DMA); //irq_set_enabled(DMA_IRQ_0, true); @@ -504,6 +512,14 @@ fn main() -> ! { pac::NVIC::unmask(pac::Interrupt::DMA_IRQ_0); info!("IRQs enabled"); + + DMA_PERIPH + .as_mut() + .unwrap() + .multi_chan_trigger + .write(|w| w.bits((1 << PIXEL_DMA_CHAN) | (1 << TIMING_DMA_CHAN))); + + info!("DMA enabled"); } info!("DMA set-up complete"); @@ -591,8 +607,6 @@ unsafe fn DMA_IRQ_0() { }; CURRENT_TIMING_LINE.store(timing_line, Ordering::Relaxed); - info!("Sync {}", timing_line); - let buffer = if (timing_line == 0) || (timing_line == 1) { // V-Sync pulse &TIMING_BUFFER.vblank_sync_buffer @@ -615,11 +629,9 @@ unsafe fn DMA_IRQ_0() { if pixel_dma_chan_irq { dma.ints0.write(|w| w.bits(1 << PIXEL_DMA_CHAN)); - let display_line = CURRENT_DISPLAY_LINE.load(Ordering::Relaxed); - - info!("Line {}", display_line); + let old_display_line = CURRENT_DISPLAY_LINE.load(Ordering::Relaxed); - if display_line == 479 { + if old_display_line == 479 { // Set ourselves three lines early CURRENT_DISPLAY_LINE.store(-3, Ordering::Relaxed); NEW_FRAME.store(true, Ordering::Relaxed); @@ -634,25 +646,29 @@ unsafe fn DMA_IRQ_0() { .ch_al3_read_addr_trig .write(|w| w.bits(PIXEL_DATA_ZERO_BUFFER.as_ptr() as usize as u32)) } else { - let display_line = display_line + 1; + let display_line = old_display_line + 1; CURRENT_DISPLAY_LINE.store(display_line, Ordering::Relaxed); if display_line == 0 { - // Our first line - turn on read address increment on the DMA + // Our first visible line - turn on read address increment on the DMA dma.ch[PIXEL_DMA_CHAN] .ch_ctrl_trig .modify(|_r, w| w.incr_read().set_bit()); } + // Set the DMA load address according to which line we are on if display_line < 0 { + // Invisible line (last part of the front-porch) dma.ch[PIXEL_DMA_CHAN] .ch_al3_read_addr_trig .write(|w| w.bits(PIXEL_DATA_ZERO_BUFFER.as_ptr() as usize as u32)) - } else if (display_line & 1) == 1 { + } else if (display_line & 4) == 4 { + // Odd visible line dma.ch[PIXEL_DMA_CHAN] .ch_al3_read_addr_trig .write(|w| w.bits(PIXEL_DATA_BUFFER_ODD.as_ptr())) } else { + // Even visible line dma.ch[PIXEL_DMA_CHAN] .ch_al3_read_addr_trig .write(|w| w.bits(PIXEL_DATA_BUFFER_EVEN.as_ptr())) @@ -664,7 +680,7 @@ unsafe fn DMA_IRQ_0() { impl LineBuffer { /// Convert the line buffer to a 32-bit address that the DMA engine understands. fn as_ptr(&self) -> u32 { - self as *const _ as usize as u32 + self.pixels.as_ptr() as usize as u32 + 4 } } From 8db057706c86a642e3df601c92e255cc7ca08435 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Tue, 16 Nov 2021 19:11:41 +0000 Subject: [PATCH 20/41] Generates stripes in the main thread. Removed the -3 stuff for pre-buffering display lines. Might add it later once we're rendering text. Also re-ordered the sync pulses to scanlines 0..=479 are the visible lines and the v-sync pulse comes after. --- src/main.rs | 244 ++++++++++++++++++++++------------------------------ 1 file changed, 102 insertions(+), 142 deletions(-) diff --git a/src/main.rs b/src/main.rs index f017f18..4c4bcc6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -37,7 +37,7 @@ // Imports // ----------------------------------------------------------------------------- -use core::sync::atomic::{AtomicBool, AtomicI16, AtomicU16, Ordering}; +use core::sync::atomic::{AtomicU16, Ordering}; use cortex_m_rt::entry; use defmt::*; use defmt_rtt as _; @@ -143,11 +143,8 @@ static TIMING_BUFFER: TimingBuffer = TimingBuffer { /// Tracks which scanline we are currently on (for timing purposes => it goes 0..=524) static CURRENT_TIMING_LINE: AtomicU16 = AtomicU16::new(0); -/// Tracks which scanline we are drawing (for pixel purposes => it goes -3..=479) -static CURRENT_DISPLAY_LINE: AtomicI16 = AtomicI16::new(-3); - -/// Set to true when we are at the top of the frame (and in the v-blank) -static NEW_FRAME: AtomicBool = AtomicBool::new(false); +/// Tracks which scanline we are currently on (for pixel purposes => it goes 0..=479) +static CURRENT_DISPLAY_LINE: AtomicU16 = AtomicU16::new(0); /// Somewhere to stash the DMA controller object, so the IRQ can find it static mut DMA_PERIPH: Option = None; @@ -158,9 +155,6 @@ static mut TIMING_DMA_CHAN: usize = 0; /// DMA channel for the pixel FIFO static mut PIXEL_DMA_CHAN: usize = 1; -/// A dummy value for the DMA to read whilst we wait for the visible portion to start -static mut PIXEL_DATA_ZERO_BUFFER: [u32; 1] = [0xFFFF_FFFF]; - /// 12-bit pixels for the even scan-lines (0, 2, 4 ... 478). Defaults to black. static mut PIXEL_DATA_BUFFER_EVEN: LineBuffer = LineBuffer { // The length is one less than the number of pixel pairs because of the @@ -190,17 +184,9 @@ fn main() -> ! { // Grab the singleton containing all the generic Cortex-M peripherals let core = pac::CorePeripherals::take().unwrap(); - unsafe { - PIXEL_DATA_BUFFER_EVEN.pixels[0] = NUM_PIXEL_PAIRS as u32 - 1; - for px in PIXEL_DATA_BUFFER_EVEN.pixels[1..].iter_mut() { - *px = 0x0FFF_0000; - } - - PIXEL_DATA_BUFFER_ODD.pixels[0] = NUM_PIXEL_PAIRS as u32 - 1; - for px in PIXEL_DATA_BUFFER_ODD.pixels[1..].iter_mut() { - *px = 0x0000_0FFF; - } - } + // Reset the DMA engine + pac.RESETS.reset.modify(|_, w| w.dma().clear_bit()); + while pac.RESETS.reset_done.read().dma().bit_is_clear() {} // Needed by the clock setup let mut watchdog = hal::watchdog::Watchdog::new(pac.WATCHDOG); @@ -283,6 +269,18 @@ fn main() -> ! { let _blue2 = pins.gpio12.into_mode::(); let _blue3 = pins.gpio13.into_mode::(); + // Set up a checker board in the line buffers + unsafe { + PIXEL_DATA_BUFFER_EVEN.pixels[0] = NUM_PIXEL_PAIRS as u32 - 1; + for px in PIXEL_DATA_BUFFER_EVEN.pixels[1..].iter_mut() { + *px = 0x0FFF_0000; + } + PIXEL_DATA_BUFFER_ODD.pixels[0] = NUM_PIXEL_PAIRS as u32 - 1; + for px in PIXEL_DATA_BUFFER_ODD.pixels[1..].iter_mut() { + *px = 0x0000_0FFF; + } + } + // Grab PIO0 and the state machines it contains let (mut pio, sm0, sm1, _sm2, _sm3) = pac.PIO0.split(&mut pac.RESETS); @@ -325,10 +323,10 @@ fn main() -> ! { // the RGB output pins. On a Neotron Pico, there are 12 (4 Red, 4 Green and // 4 Blue) - so we set autopull to 12, and each value should be 12-bits long. // - // Currently the FIFO support is disabled and it just puts out 640 fixed - // 12-bit pixels per scanline. + // Currently the FIFO supplies only the pixels, not the length value. When + // we read the length from the FIFO as well, all hell breaks loose. // - // Note autopull should be set to 12-bits, OSR is set to shift right. + // Note autopull should be set to 32-bits, OSR is set to shift right. let pixel_program = pio_proc::pio!( 32, " @@ -336,8 +334,8 @@ fn main() -> ! { ; Wait for timing state machine to start visible line wait 1 irq 0 ; Read the line length (in pixel-pairs) - ; out x, 32 - set x, 31 + ; out x, 32 ; BROKEN + set x, 31 ; Use fixed value instead loop1: ; Write out first pixel - takes 5 clocks per pixel (4 pixels) out pins, 16 [19] @@ -360,7 +358,7 @@ fn main() -> ! { // | 3 | out exec, 16 | wait 1 irq 0 | // | 4 | | wait 1 irq 0 | // | 5 | jmp x-- | wait 1 irq 0 | - // | 6 | | out x, 12 | + // | 6 | | set x, 31 | // | 7 | | out pins, 16 [4] | // | 8 | | .. | // | 9 | | .. | @@ -404,20 +402,10 @@ fn main() -> ! { pixel_sm.set_pindirs((2..=13).map(|x| (x, hal_pio::PinDir::Output))); unsafe { - pac.RESETS.reset.modify(|_, w| w.dma().clear_bit()); - while pac.RESETS.reset_done.read().dma().bit_is_clear() {} - - // dma_channel_config timing_dma_chan_config = dma_channel_get_default_config(timing_dma_chan); - - // Transfer 32 bits at a time - // channel_config_set_transfer_data_size(&timing_dma_chan_config, DMA_SIZE_32); - // Increment read to go through the sync timing command buffer - // channel_config_set_read_increment(&timing_dma_chan_config, true); - // Don't increment write so we always transfer to the PIO FIFO - // channel_config_set_write_increment(&timing_dma_chan_config, false); - // Transfer when there's space in the sync SM FIFO - // channel_config_set_dreq(&timing_dma_chan_config, pio_get_dreq(pio, sync_sm, true)); - pac.DMA.ch[TIMING_DMA_CHAN].ch_ctrl_trig.modify(|_r, w| { + // Read from the timing buffer and write to the timing FIFO. We get an + // IRQ when the transfer is complete (i.e. when line has been fully + // loaded). + pac.DMA.ch[TIMING_DMA_CHAN].ch_ctrl_trig.write(|w| { w.data_size().size_word(); w.incr_read().set_bit(); w.incr_write().clear_bit(); @@ -431,40 +419,20 @@ fn main() -> ! { w.sniff_en().clear_bit(); w }); - - // Setup the channel and set it going - // dma_channel_configure( - // timing_dma_chan, - // &timing_dma_chan_config, - // &pio->txf[sync_sm], // Write to PIO TX FIFO - // vblank_sync_buffer, // Begin with vblank sync line - // 4, // 4 command words in each sync buffer - // false // Don't start yet - // ); - // set_read_addr pac.DMA.ch[TIMING_DMA_CHAN] .ch_read_addr - .write(|w| w.bits(TIMING_BUFFER.vblank_sync_buffer.data.as_ptr() as usize as u32)); - // set_write_addr + .write(|w| w.bits(TIMING_BUFFER.visible_line.data.as_ptr() as usize as u32)); pac.DMA.ch[TIMING_DMA_CHAN] .ch_write_addr .write(|w| w.bits(timing_fifo.dma_address())); - // set_trans_count pac.DMA.ch[TIMING_DMA_CHAN] .ch_trans_count .write(|w| w.bits(4)); - // Transfer 32 bits at a time - // channel_config_set_transfer_data_size(&pixel_dma_chan_config, DMA_SIZE_32); - // Increment read to go through the pixel data buffer - // channel_config_set_read_increment(&pixel_dma_chan_config, false); - // Don't increment write so we always transfer to the PIO FIFO - // channel_config_set_write_increment(&pixel_dma_chan_config, false); - // Transfer when there's space in the pixel SM FIFO - // channel_config_set_dreq(&pixel_dma_chan_config, pio_get_dreq(pio, line_sm, true)); - pac.DMA.ch[PIXEL_DMA_CHAN].ch_ctrl_trig.modify(|_r, w| { + // Read from the pixel buffer (even first) and write to the pixel FIFO + pac.DMA.ch[PIXEL_DMA_CHAN].ch_ctrl_trig.write(|w| { w.data_size().size_word(); - w.incr_read().clear_bit(); + w.incr_read().set_bit(); w.incr_write().clear_bit(); w.treq_sel().bits(pixel_fifo.dreq_value()); w.chain_to().bits(PIXEL_DMA_CHAN as u8); @@ -476,43 +444,30 @@ fn main() -> ! { w.sniff_en().clear_bit(); w }); - // Setup the channel and set it going - // dma_channel_configure( - // pixel_dma_chan, - // &pixel_dma_chan_config, - // &pio->txf[line_sm], // Write to PIO TX FIFO - // &line_data_zero_buffer, // First line output will be white line - // 160, // Transfer complete contents of `line_data_buffer` - // false // Don't start yet - // ); pac.DMA.ch[PIXEL_DMA_CHAN] .ch_read_addr - .write(|w| w.bits(PIXEL_DATA_ZERO_BUFFER.as_ptr() as usize as u32)); - // set_write_addr + .write(|w| w.bits(PIXEL_DATA_BUFFER_EVEN.as_ptr())); pac.DMA.ch[PIXEL_DMA_CHAN] .ch_write_addr .write(|w| w.bits(pixel_fifo.dma_address())); - // set_trans_count pac.DMA.ch[PIXEL_DMA_CHAN] .ch_trans_count .write(|w| w.bits(NUM_PIXEL_PAIRS as u32)); - - // Setup interrupt handler for line and sync DMA channels - //dma_channel_set_irq0_enabled(pixel_dma_chan, true); - //dma_channel_set_irq0_enabled(timing_dma_chan, true); - pac.DMA.inte0.modify(|_r, w| { + pac.DMA.inte0.write(|w| { w.inte0() .bits((1 << PIXEL_DMA_CHAN) | (1 << TIMING_DMA_CHAN)) }); + // Hand off the DMA peripheral to the interrupt DMA_PERIPH = Some(pac.DMA); - //irq_set_enabled(DMA_IRQ_0, true); + // Enable the interupts (DMA_PERIPH has to be set first) pac::NVIC::unpend(pac::Interrupt::DMA_IRQ_0); pac::NVIC::unmask(pac::Interrupt::DMA_IRQ_0); info!("IRQs enabled"); + // Enable the DMA DMA_PERIPH .as_mut() .unwrap() @@ -529,13 +484,32 @@ fn main() -> ! { info!("State Machines running"); + let mut last_line = 0; + let mut frame_count = 0; loop { cortex_m::asm::wfi(); - info!( - "Line is {}/480, {}/525", - CURRENT_DISPLAY_LINE.load(Ordering::Relaxed), - CURRENT_TIMING_LINE.load(Ordering::Relaxed) - ); + let try_line = CURRENT_DISPLAY_LINE.load(Ordering::Relaxed); + if try_line != last_line { + last_line = try_line; + let next_line = if last_line == 479 { + info!("Frame {}", frame_count); + frame_count += 1; + 0 + } else { + last_line + 1 + }; + // new line - do some painting! + let px_buf = unsafe { + if (next_line & 1) == 1 { + &mut PIXEL_DATA_BUFFER_ODD + } else { + &mut PIXEL_DATA_BUFFER_EVEN + } + }; + for px in px_buf.pixels.iter_mut().skip(1) { + *px = u32::from(next_line) | u32::from(next_line) << 16; + } + } } } @@ -600,26 +574,32 @@ unsafe fn DMA_IRQ_0() { dma.ints0.write(|w| w.bits(1 << TIMING_DMA_CHAN)); let old_timing_line = CURRENT_TIMING_LINE.load(Ordering::Relaxed); - let timing_line = if old_timing_line < 524 { - old_timing_line + 1 - } else { + let timing_line = if old_timing_line == 524 { + // 524 -> 0 0 + } else { + // n -> n + 1 + old_timing_line + 1 }; CURRENT_TIMING_LINE.store(timing_line, Ordering::Relaxed); - let buffer = if (timing_line == 0) || (timing_line == 1) { - // V-Sync pulse - &TIMING_BUFFER.vblank_sync_buffer - } else if timing_line < 32 { - // VGA back porch following VGA sync pulse - &TIMING_BUFFER.vblank_porch_buffer - } else if timing_line < 515 { - // Visible lines (including three lines of the back porch we treat - // as visible to get ourselves up and running) - &TIMING_BUFFER.visible_line - } else { - // Front porch - &TIMING_BUFFER.vblank_porch_buffer + let buffer = match timing_line { + 0..=479 => { + // Visible lines + &TIMING_BUFFER.visible_line + } + 480..=489 => { + // VGA front porch before VGA sync pulse + &TIMING_BUFFER.vblank_porch_buffer + } + 490..=491 => { + // Sync pulse + &TIMING_BUFFER.vblank_sync_buffer + } + 492.. => { + // VGA back porch following VGA sync pulse + &TIMING_BUFFER.vblank_porch_buffer + } }; dma.ch[TIMING_DMA_CHAN] .ch_al3_read_addr_trig @@ -629,50 +609,29 @@ unsafe fn DMA_IRQ_0() { if pixel_dma_chan_irq { dma.ints0.write(|w| w.bits(1 << PIXEL_DMA_CHAN)); + // A pixel DMA transfer is now complete. This only fires on lines 0..=480. + let old_display_line = CURRENT_DISPLAY_LINE.load(Ordering::Relaxed); + let next_display_line = if old_display_line == 479 { + // 479 -> 0 + 0 + } else { + // n -> n + 1 + old_display_line + 1 + }; + CURRENT_DISPLAY_LINE.store(next_display_line, Ordering::Relaxed); - if old_display_line == 479 { - // Set ourselves three lines early - CURRENT_DISPLAY_LINE.store(-3, Ordering::Relaxed); - NEW_FRAME.store(true, Ordering::Relaxed); - // Setup Line DMA channel to read zero lines for dummy lines and set it going. - // Disable read increment so just read zero over and over for dummy lines. - // DMA won't actually begin until line PIO starts consuming it in the next - // frame. - dma.ch[PIXEL_DMA_CHAN] - .ch_ctrl_trig - .modify(|_r, w| w.incr_read().clear_bit()); + // Set the DMA load address according to which line we are on + if (next_display_line & 1) == 1 { + // Odd visible line is next dma.ch[PIXEL_DMA_CHAN] .ch_al3_read_addr_trig - .write(|w| w.bits(PIXEL_DATA_ZERO_BUFFER.as_ptr() as usize as u32)) + .write(|w| w.bits(PIXEL_DATA_BUFFER_ODD.as_ptr())) } else { - let display_line = old_display_line + 1; - CURRENT_DISPLAY_LINE.store(display_line, Ordering::Relaxed); - - if display_line == 0 { - // Our first visible line - turn on read address increment on the DMA - dma.ch[PIXEL_DMA_CHAN] - .ch_ctrl_trig - .modify(|_r, w| w.incr_read().set_bit()); - } - - // Set the DMA load address according to which line we are on - if display_line < 0 { - // Invisible line (last part of the front-porch) - dma.ch[PIXEL_DMA_CHAN] - .ch_al3_read_addr_trig - .write(|w| w.bits(PIXEL_DATA_ZERO_BUFFER.as_ptr() as usize as u32)) - } else if (display_line & 4) == 4 { - // Odd visible line - dma.ch[PIXEL_DMA_CHAN] - .ch_al3_read_addr_trig - .write(|w| w.bits(PIXEL_DATA_BUFFER_ODD.as_ptr())) - } else { - // Even visible line - dma.ch[PIXEL_DMA_CHAN] - .ch_al3_read_addr_trig - .write(|w| w.bits(PIXEL_DATA_BUFFER_EVEN.as_ptr())) - } + // Even visible line is next + dma.ch[PIXEL_DMA_CHAN] + .ch_al3_read_addr_trig + .write(|w| w.bits(PIXEL_DATA_BUFFER_EVEN.as_ptr())) } } } @@ -680,6 +639,7 @@ unsafe fn DMA_IRQ_0() { impl LineBuffer { /// Convert the line buffer to a 32-bit address that the DMA engine understands. fn as_ptr(&self) -> u32 { + // NB: skip the length field self.pixels.as_ptr() as usize as u32 + 4 } } From c2e84f52a704894cf228d4a2c2c8ddaad39a6f50 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Tue, 16 Nov 2021 19:12:06 +0000 Subject: [PATCH 21/41] Set the SMPS to low-efficiency, low-noise mode. Needs a pin rename in the Pico BSP. --- src/main.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main.rs b/src/main.rs index 4c4bcc6..169a93f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -41,6 +41,7 @@ use core::sync::atomic::{AtomicU16, Ordering}; use cortex_m_rt::entry; use defmt::*; use defmt_rtt as _; +use embedded_hal::digital::v2::OutputPin; use embedded_time::rate::*; use git_version::git_version; use hal::clocks::Clock; @@ -251,6 +252,10 @@ fn main() -> ! { &mut pac.RESETS, ); + // Disable power save mode to force SMPS into low-efficiency, low-noise mode. + let mut b_power_save = pins.b_power_save.into_push_pull_output(); + b_power_save.set_high().unwrap(); + info!("Pins OK"); // Give H-Sync, V-Sync and 12 RGB colour pins to PIO0 to output video From f8022a75d37183144e4c454d32665349b9cd6802 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Tue, 16 Nov 2021 21:33:06 +0000 Subject: [PATCH 22/41] Fill all 640 pixels per line with colour. --- src/main.rs | 118 +++++++++++++++++++++++++++++++++------------------- 1 file changed, 75 insertions(+), 43 deletions(-) diff --git a/src/main.rs b/src/main.rs index 169a93f..4ee81e8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -37,7 +37,7 @@ // Imports // ----------------------------------------------------------------------------- -use core::sync::atomic::{AtomicU16, Ordering}; +use core::sync::atomic::{AtomicBool, AtomicU16, Ordering}; use cortex_m_rt::entry; use defmt::*; use defmt_rtt as _; @@ -71,11 +71,26 @@ struct TimingBuffer { vblank_sync_buffer: ScanlineTimingBuffer, } -const NUM_PIXEL_PAIRS: usize = 32; +/// How many pixels per scan-line. Adjust the pixel PIO program to run at the +/// right speed to the screen is filled. For example, if this is only 320 but +/// you are aiming at 640x480, make the pixel PIO take twice as long per +/// pixel. +const NUM_PIXELS_PER_LINE: usize = 640; + +/// How many pixel pairs we send out. Each pixel is two 12-bit values packed +/// into one 32-bit word. This is to make more efficient use of DMA and FIFO +/// resources. +const NUM_PIXEL_PAIRS_PER_LINE: usize = NUM_PIXELS_PER_LINE / 2; + +/// Number of lines on screen. +const NUM_LINES: u16 = 480; + +/// Index of the last line +const LAST_LINE: u16 = NUM_LINES - 1; #[repr(C, align(16))] struct LineBuffer { - pixels: [u32; NUM_PIXEL_PAIRS + 1], + pixels: [u32; NUM_PIXEL_PAIRS_PER_LINE + 1], } // ----------------------------------------------------------------------------- @@ -144,9 +159,12 @@ static TIMING_BUFFER: TimingBuffer = TimingBuffer { /// Tracks which scanline we are currently on (for timing purposes => it goes 0..=524) static CURRENT_TIMING_LINE: AtomicU16 = AtomicU16::new(0); -/// Tracks which scanline we are currently on (for pixel purposes => it goes 0..=479) +/// Tracks which scanline we are currently on (for pixel purposes => it goes 0..NUM_LINES) static CURRENT_DISPLAY_LINE: AtomicU16 = AtomicU16::new(0); +/// Set to `true` when DMA of previous line is complete and next line is scheduled. +static DMA_READY: AtomicBool = AtomicBool::new(false); + /// Somewhere to stash the DMA controller object, so the IRQ can find it static mut DMA_PERIPH: Option = None; @@ -156,18 +174,18 @@ static mut TIMING_DMA_CHAN: usize = 0; /// DMA channel for the pixel FIFO static mut PIXEL_DMA_CHAN: usize = 1; -/// 12-bit pixels for the even scan-lines (0, 2, 4 ... 478). Defaults to black. +/// 12-bit pixels for the even scan-lines (0, 2, 4 ... NUM_LINES - 2). Defaults to black. static mut PIXEL_DATA_BUFFER_EVEN: LineBuffer = LineBuffer { // The length is one less than the number of pixel pairs because of the // way the jmp works in the PIO program (it's a do-while loop). - pixels: [0; NUM_PIXEL_PAIRS + 1], + pixels: [0; NUM_PIXEL_PAIRS_PER_LINE + 1], }; -/// 12-bit pixels for the odd scan-lines (1, 3, 5 ... 479). Defaults to white. +/// 12-bit pixels for the odd scan-lines (1, 3, 5 ... NUM_LINES-1). Defaults to white. static mut PIXEL_DATA_BUFFER_ODD: LineBuffer = LineBuffer { // The length is one less than the number of pixel pairs because of the // way the jmp works in the PIO program (it's a do-while loop). - pixels: [0; NUM_PIXEL_PAIRS + 1], + pixels: [0; NUM_PIXEL_PAIRS_PER_LINE + 1], }; // ----------------------------------------------------------------------------- @@ -178,6 +196,8 @@ static mut PIXEL_DATA_BUFFER_ODD: LineBuffer = LineBuffer { /// `.bss` and `.data` sections have been initialised. #[entry] fn main() -> ! { + cortex_m::interrupt::disable(); + info!("Neotron BIOS {} starting...", GIT_VERSION); // Grab the singleton containing all the RP2040 peripherals @@ -185,8 +205,11 @@ fn main() -> ! { // Grab the singleton containing all the generic Cortex-M peripherals let core = pac::CorePeripherals::take().unwrap(); - // Reset the DMA engine - pac.RESETS.reset.modify(|_, w| w.dma().clear_bit()); + // Reset the DMA engine. If we don't do this, starting from probe-run + // (as opposed to a cold-start) is unreliable. + pac.RESETS.reset.modify(|_r, w| w.dma().set_bit()); + cortex_m::asm::nop(); + pac.RESETS.reset.modify(|_r, w| w.dma().clear_bit()); while pac.RESETS.reset_done.read().dma().bit_is_clear() {} // Needed by the clock setup @@ -276,11 +299,11 @@ fn main() -> ! { // Set up a checker board in the line buffers unsafe { - PIXEL_DATA_BUFFER_EVEN.pixels[0] = NUM_PIXEL_PAIRS as u32 - 1; + PIXEL_DATA_BUFFER_EVEN.pixels[0] = NUM_PIXEL_PAIRS_PER_LINE as u32 - 1; for px in PIXEL_DATA_BUFFER_EVEN.pixels[1..].iter_mut() { *px = 0x0FFF_0000; } - PIXEL_DATA_BUFFER_ODD.pixels[0] = NUM_PIXEL_PAIRS as u32 - 1; + PIXEL_DATA_BUFFER_ODD.pixels[0] = NUM_PIXEL_PAIRS_PER_LINE as u32 - 1; for px in PIXEL_DATA_BUFFER_ODD.pixels[1..].iter_mut() { *px = 0x0000_0FFF; } @@ -339,13 +362,12 @@ fn main() -> ! { ; Wait for timing state machine to start visible line wait 1 irq 0 ; Read the line length (in pixel-pairs) - ; out x, 32 ; BROKEN - set x, 31 ; Use fixed value instead + out x, 32 loop1: - ; Write out first pixel - takes 5 clocks per pixel (4 pixels) - out pins, 16 [19] - ; Write out second pixel - takes 5 clocks per pixel (4 pixels, allowing one for the jump) - out pins, 16 [18] + ; Write out first pixel - takes 5 clocks per pixel (double-width) + out pins, 16 [4] + ; Write out second pixel - takes 5 clocks per pixel (double-width, allowing one for the jump) + out pins, 16 [3] ; Repeat until all pixel pairs sent jmp x-- loop1 ; Clear all pins after visible section @@ -457,7 +479,7 @@ fn main() -> ! { .write(|w| w.bits(pixel_fifo.dma_address())); pac.DMA.ch[PIXEL_DMA_CHAN] .ch_trans_count - .write(|w| w.bits(NUM_PIXEL_PAIRS as u32)); + .write(|w| w.bits(NUM_PIXEL_PAIRS_PER_LINE as u32 + 1)); pac.DMA.inte0.write(|w| { w.inte0() .bits((1 << PIXEL_DMA_CHAN) | (1 << TIMING_DMA_CHAN)) @@ -466,6 +488,8 @@ fn main() -> ! { // Hand off the DMA peripheral to the interrupt DMA_PERIPH = Some(pac.DMA); + cortex_m::interrupt::enable(); + // Enable the interupts (DMA_PERIPH has to be set first) pac::NVIC::unpend(pac::Interrupt::DMA_IRQ_0); pac::NVIC::unmask(pac::Interrupt::DMA_IRQ_0); @@ -489,30 +513,37 @@ fn main() -> ! { info!("State Machines running"); - let mut last_line = 0; let mut frame_count = 0; loop { cortex_m::asm::wfi(); - let try_line = CURRENT_DISPLAY_LINE.load(Ordering::Relaxed); - if try_line != last_line { - last_line = try_line; - let next_line = if last_line == 479 { + if DMA_READY.load(Ordering::Relaxed) { + DMA_READY.store(false, Ordering::Relaxed); + let current_line = CURRENT_DISPLAY_LINE.load(Ordering::Relaxed); + if current_line == 0 { info!("Frame {}", frame_count); frame_count += 1; - 0 - } else { - last_line + 1 - }; + } // new line - do some painting! let px_buf = unsafe { - if (next_line & 1) == 1 { + if (current_line & 1) == 0 { &mut PIXEL_DATA_BUFFER_ODD } else { &mut PIXEL_DATA_BUFFER_EVEN } }; - for px in px_buf.pixels.iter_mut().skip(1) { - *px = u32::from(next_line) | u32::from(next_line) << 16; + let qtr = NUM_PIXEL_PAIRS_PER_LINE / 4; + let red = ((frame_count + (current_line as u32)) & 0x0F) * 0x0001_0001; + for x in 0..qtr { + px_buf.pixels[x + 1] = 0x0FF0_0FF0 + red; + } + for x in qtr..(2 * qtr) { + px_buf.pixels[x + 1] = 0x0F00_0F00 + red; + } + for x in (2 * qtr)..(3 * qtr) { + px_buf.pixels[x + 1] = 0x00F0_00F0 + red; + } + for x in (3 * qtr)..(4 * qtr) { + px_buf.pixels[x + 1] = 0x0000_0000 + red; } } } @@ -589,11 +620,11 @@ unsafe fn DMA_IRQ_0() { CURRENT_TIMING_LINE.store(timing_line, Ordering::Relaxed); let buffer = match timing_line { - 0..=479 => { + 0..=LAST_LINE => { // Visible lines &TIMING_BUFFER.visible_line } - 480..=489 => { + NUM_LINES..=489 => { // VGA front porch before VGA sync pulse &TIMING_BUFFER.vblank_porch_buffer } @@ -616,17 +647,15 @@ unsafe fn DMA_IRQ_0() { // A pixel DMA transfer is now complete. This only fires on lines 0..=480. - let old_display_line = CURRENT_DISPLAY_LINE.load(Ordering::Relaxed); - let next_display_line = if old_display_line == 479 { - // 479 -> 0 - 0 - } else { - // n -> n + 1 - old_display_line + 1 + let mut next_display_line = CURRENT_DISPLAY_LINE.load(Ordering::Relaxed) + 1; + if next_display_line > LAST_LINE { + next_display_line = 0; }; - CURRENT_DISPLAY_LINE.store(next_display_line, Ordering::Relaxed); - // Set the DMA load address according to which line we are on + // Set the DMA load address according to which line we are on. We use + // the 'trigger' alias to restart the DMA at the same time as we + // write the new read address. The DMA had stopped because the + // previous line was transferred completely. if (next_display_line & 1) == 1 { // Odd visible line is next dma.ch[PIXEL_DMA_CHAN] @@ -638,6 +667,9 @@ unsafe fn DMA_IRQ_0() { .ch_al3_read_addr_trig .write(|w| w.bits(PIXEL_DATA_BUFFER_EVEN.as_ptr())) } + + CURRENT_DISPLAY_LINE.store(next_display_line, Ordering::Relaxed); + DMA_READY.store(true, Ordering::Relaxed); } } @@ -645,7 +677,7 @@ impl LineBuffer { /// Convert the line buffer to a 32-bit address that the DMA engine understands. fn as_ptr(&self) -> u32 { // NB: skip the length field - self.pixels.as_ptr() as usize as u32 + 4 + self.pixels.as_ptr() as usize as u32 } } From 93d6ab3320e36f11160731d31d94498d683d35a5 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Tue, 16 Nov 2021 21:46:53 +0000 Subject: [PATCH 23/41] Renamed FIFO access function in rp-hal. --- src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 4ee81e8..30c6070 100644 --- a/src/main.rs +++ b/src/main.rs @@ -451,7 +451,7 @@ fn main() -> ! { .write(|w| w.bits(TIMING_BUFFER.visible_line.data.as_ptr() as usize as u32)); pac.DMA.ch[TIMING_DMA_CHAN] .ch_write_addr - .write(|w| w.bits(timing_fifo.dma_address())); + .write(|w| w.bits(timing_fifo.fifo_address() as usize as u32)); pac.DMA.ch[TIMING_DMA_CHAN] .ch_trans_count .write(|w| w.bits(4)); @@ -476,7 +476,7 @@ fn main() -> ! { .write(|w| w.bits(PIXEL_DATA_BUFFER_EVEN.as_ptr())); pac.DMA.ch[PIXEL_DMA_CHAN] .ch_write_addr - .write(|w| w.bits(pixel_fifo.dma_address())); + .write(|w| w.bits(pixel_fifo.fifo_address() as usize as u32)); pac.DMA.ch[PIXEL_DMA_CHAN] .ch_trans_count .write(|w| w.bits(NUM_PIXEL_PAIRS_PER_LINE as u32 + 1)); From 9c9131f82075532168d89ae92072614cca20fa83 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Tue, 16 Nov 2021 23:03:21 +0000 Subject: [PATCH 24/41] Drop to 640x400 @ 70 Hz, and add some 1337 ASCII art. --- src/main.rs | 83 ++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 63 insertions(+), 20 deletions(-) diff --git a/src/main.rs b/src/main.rs index 30c6070..8fc85dd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -55,6 +55,8 @@ use pico::{ }, }; +mod freebsd_cp850; + // ----------------------------------------------------------------------------- // Types // ----------------------------------------------------------------------------- @@ -83,7 +85,7 @@ const NUM_PIXELS_PER_LINE: usize = 640; const NUM_PIXEL_PAIRS_PER_LINE: usize = NUM_PIXELS_PER_LINE / 2; /// Number of lines on screen. -const NUM_LINES: u16 = 480; +const NUM_LINES: u16 = 400; /// Index of the last line const LAST_LINE: u16 = NUM_LINES - 1; @@ -93,6 +95,35 @@ struct LineBuffer { pixels: [u32; NUM_PIXEL_PAIRS_PER_LINE + 1], } + +static mut CHAR_ARRAY: [u8; 2000] = +*b"\ +\xDA\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xBF\ +\xB3Neotron Pico Booting \xB3\ +\xB3 \xB3\ +\xB3 \xB3\ +\xB3 \xB3\ +\xB3 \xB3\ +\xB3 \xDb\xDb\xDb\xBB\xB2\xB2\xDb\xDb\xBB\xDb\xDb\xDb\xDb\xDb\xDb\xDb\xBB\xB2\xDb\xDb\xDb\xDb\xDb\xBB\xB2\xDb\xDb\xDb\xDb\xDb\xDb\xDb\xDb\xBB\xDb\xDb\xDb\xDb\xDb\xDb\xBB\xB2\xB2\xDb\xDb\xDb\xDb\xDb\xBB\xB2\xDb\xDb\xDb\xBB\xB2\xB2\xDb\xDb\xBB \xB3\ +\xB3 \xDb\xDb\xDb\xDb\xBB\xB2\xDb\xDb\xBA\xDb\xDb\xC9\xCD\xCD\xCD\xCD\xBC\xDb\xDb\xC9\xCD\xCD\xDb\xDb\xBB\xC8\xCD\xCD\xDb\xDb\xC9\xCD\xCD\xBC\xDb\xDb\xC9\xCD\xCD\xDb\xDb\xBB\xDb\xDb\xC9\xCD\xCD\xDb\xDb\xBB\xDb\xDb\xDb\xDb\xBB\xB2\xDb\xDb\xBA \xB3\ +\xB3 \xDb\xDb\xC9\xDb\xDb\xBB\xDb\xDb\xBA\xDb\xDb\xDb\xDb\xDb\xBB\xB2\xB2\xDb\xDb\xBA\xB2\xB2\xDb\xDb\xBA\xB2\xB2\xB2\xDb\xDb\xBA\xB2\xB2\xB2\xDb\xDb\xDb\xDb\xDb\xDb\xC9\xBC\xDb\xDb\xBA\xB2\xB2\xDb\xDb\xBA\xDb\xDb\xC9\xDb\xDb\xBB\xDb\xDb\xBA \xB3\ +\xB3 \xDb\xDb\xBA\xC8\xDb\xDb\xDb\xDb\xBA\xDb\xDb\xC9\xCD\xCD\xBC\xB2\xB2\xDb\xDb\xBA\xB2\xB2\xDb\xDb\xBA\xB2\xB2\xB2\xDb\xDb\xBA\xB2\xB2\xB2\xDb\xDb\xC9\xCD\xCD\xDb\xDb\xBB\xDb\xDb\xBA\xB2\xB2\xDb\xDb\xBA\xDb\xDb\xBA\xC8\xDb\xDb\xDb\xDb\xBA \xB3\ +\xB3 \xDb\xDb\xBA\xB2\xC8\xDb\xDb\xDb\xBA\xDb\xDb\xDb\xDb\xDb\xDb\xDb\xBB\xC8\xDb\xDb\xDb\xDb\xDb\xC9\xBC\xB2\xB2\xB2\xDb\xDb\xBA\xB2\xB2\xB2\xDb\xDb\xBA\xB2\xB2\xDb\xDb\xBA\xC8\xDb\xDb\xDb\xDb\xDb\xC9\xBC\xDb\xDb\xBA\xB2\xC8\xDb\xDb\xDb\xBA \xB3\ +\xB3 \xB3\ +\xB3 \xB3\ +\xB3 \xB3\ +\xB3 \xB3\ +\xB3 \xB3\ +\xB3 \xB3\ +\xB3 \xB3\ +\xB3 \xB3\ +\xB3 \xB3\ +\xB3 \xB3\ +\xB3 \xB3\ +\xB3 \xB3\ +\xB3This is the end \xB3\ +\xC0\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xD9"; + // ----------------------------------------------------------------------------- // Static and Const Data // ----------------------------------------------------------------------------- @@ -156,7 +187,7 @@ static TIMING_BUFFER: TimingBuffer = TimingBuffer { }, }; -/// Tracks which scanline we are currently on (for timing purposes => it goes 0..=524) +/// Tracks which scanline we are currently on (for timing purposes => it goes 0..=449) static CURRENT_TIMING_LINE: AtomicU16 = AtomicU16::new(0); /// Tracks which scanline we are currently on (for pixel purposes => it goes 0..NUM_LINES) @@ -531,24 +562,36 @@ fn main() -> ! { &mut PIXEL_DATA_BUFFER_EVEN } }; - let qtr = NUM_PIXEL_PAIRS_PER_LINE / 4; - let red = ((frame_count + (current_line as u32)) & 0x0F) * 0x0001_0001; - for x in 0..qtr { - px_buf.pixels[x + 1] = 0x0FF0_0FF0 + red; - } - for x in qtr..(2 * qtr) { - px_buf.pixels[x + 1] = 0x0F00_0F00 + red; - } - for x in (2 * qtr)..(3 * qtr) { - px_buf.pixels[x + 1] = 0x00F0_00F0 + red; - } - for x in (3 * qtr)..(4 * qtr) { - px_buf.pixels[x + 1] = 0x0000_0000 + red; + + let text_row = (current_line / 16) as usize; + let font_row = (current_line % 16) as usize; + let text_row_offset = text_row * 80; + + if text_row < 25 { + let row_slice = unsafe { &CHAR_ARRAY[text_row_offset..text_row_offset+80] }; + let font_ptr = unsafe { freebsd_cp850::FONT_DATA.as_ptr().add(font_row) }; + + let px_ptr = px_buf.pixels[1..].as_mut_ptr(); + let mut px_idx = 0; + for ch in row_slice.iter() { + let index = (*ch as isize) * 16; + let mono_pixels = unsafe { *font_ptr.offset(index) } as usize; + // Convert from mono to RGB white + unsafe { + core::ptr::write_volatile(px_ptr.offset(px_idx + 0), LOOKUP[(mono_pixels >> 6) & 3]); + core::ptr::write_volatile(px_ptr.offset(px_idx + 1), LOOKUP[(mono_pixels >> 4) & 3]); + core::ptr::write_volatile(px_ptr.offset(px_idx + 2), LOOKUP[(mono_pixels >> 2) & 3]); + core::ptr::write_volatile(px_ptr.offset(px_idx + 3), LOOKUP[(mono_pixels >> 0) & 3]); + } + px_idx += 4; + } } } } } +static LOOKUP: [u32; 4] = [ 0x0F00_0F00, 0x00FF_0F00, 0x0F00_00FF, 0x00FF_00FF ]; + const fn make_timing(period: u32, hsync: bool, vsync: bool, raise_irq: bool) -> u32 { let command = if raise_irq { // This command sets IRQ 0. It is the same as: @@ -610,8 +653,8 @@ unsafe fn DMA_IRQ_0() { dma.ints0.write(|w| w.bits(1 << TIMING_DMA_CHAN)); let old_timing_line = CURRENT_TIMING_LINE.load(Ordering::Relaxed); - let timing_line = if old_timing_line == 524 { - // 524 -> 0 + let timing_line = if old_timing_line == 449 { + // 449 -> 0 0 } else { // n -> n + 1 @@ -624,15 +667,15 @@ unsafe fn DMA_IRQ_0() { // Visible lines &TIMING_BUFFER.visible_line } - NUM_LINES..=489 => { + NUM_LINES..=411 => { // VGA front porch before VGA sync pulse &TIMING_BUFFER.vblank_porch_buffer } - 490..=491 => { + 412..=415 => { // Sync pulse &TIMING_BUFFER.vblank_sync_buffer } - 492.. => { + 416.. => { // VGA back porch following VGA sync pulse &TIMING_BUFFER.vblank_porch_buffer } From 275330a3fb35f994b7028e3a8664f1d748655740 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Tue, 16 Nov 2021 23:05:04 +0000 Subject: [PATCH 25/41] Go back to git deps. --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f003ed6..fda4ab1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,8 +11,8 @@ version = "0.2.0" # Useful Cortex-M specific functions (e.g. SysTick) cortex-m = "0.7" # The Raspberry Pi Pico HAL -# pico = { git = "https://github.com/rp-rs/rp-hal.git" } -pico = { path = "/home/jonathan/Documents/rp-rs/rp-hal/boards/pico" } +pico = { git = "https://github.com/rp-rs/rp-hal.git" } +# pico = { path = "/home/jonathan/Documents/rp-rs/rp-hal/boards/pico" } # Cortex-M run-time (or start-up) code cortex-m-rt = "0.7" # The BIOS API we export to the OS From 91b6f113003b8836e2dcb58780723ab0ffc49256 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Tue, 16 Nov 2021 23:06:40 +0000 Subject: [PATCH 26/41] Formatting, and adding in the font. --- src/freebsd_cp850.rs | 4385 ++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 25 +- 2 files changed, 4403 insertions(+), 7 deletions(-) create mode 100644 src/freebsd_cp850.rs diff --git a/src/freebsd_cp850.rs b/src/freebsd_cp850.rs new file mode 100644 index 0000000..e167918 --- /dev/null +++ b/src/freebsd_cp850.rs @@ -0,0 +1,4385 @@ +/// The font data here must be in the same order as the `Char` enum. This is +/// the cp850-8x16 font from FreeBSD. See +/// http://web.mit.edu/freebsd/head/share/syscons/fonts/cp850-8x16.fnt +/// +/// The compilation of software known as FreeBSD is distributed under the +/// following terms: +/// +/// Copyright (c) 1992-2014 The FreeBSD Project. All rights reserved. +/// +/// Redistribution and use in source and binary forms, with or without +/// modification, are permitted provided that the following conditions +/// are met: +/// 1. Redistributions of source code must retain the above copyright +/// notice, this list of conditions and the following disclaimer. +/// 2. Redistributions in binary form must reproduce the above copyright +/// notice, this list of conditions and the following disclaimer in the +/// documentation and/or other materials provided with the distribution. +/// +/// THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +/// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +/// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +/// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +/// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +/// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +/// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +/// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +/// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +/// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +/// SUCH DAMAGE. +pub static FONT_DATA: [u8; 256 * 16] = [ + // Char::Null + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::SOH + 0b0000_0000, + 0b0000_0000, + 0b0111_1110, + 0b1000_0001, + 0b1010_0101, + 0b1000_0001, + 0b1000_0001, + 0b1011_1101, + 0b1001_1001, + 0b1000_0001, + 0b1000_0001, + 0b0111_1110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::STX + 0b0000_0000, + 0b0000_0000, + 0b0111_1110, + 0b1111_1111, + 0b1101_1011, + 0b1111_1111, + 0b1111_1111, + 0b1100_0011, + 0b1110_0111, + 0b1111_1111, + 0b1111_1111, + 0b0111_1110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::ETX + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0110_1100, + 0b1111_1110, + 0b1111_1110, + 0b1111_1110, + 0b1111_1110, + 0b0111_1100, + 0b0011_1000, + 0b0001_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::EOT + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0001_0000, + 0b0011_1000, + 0b0111_1100, + 0b1111_1110, + 0b0111_1100, + 0b0011_1000, + 0b0001_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::ENQ + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0001_1000, + 0b0011_1100, + 0b0011_1100, + 0b1110_0111, + 0b1110_0111, + 0b1110_0111, + 0b0001_1000, + 0b0001_1000, + 0b0011_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::ACK + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0001_1000, + 0b0011_1100, + 0b0111_1110, + 0b1111_1111, + 0b1111_1111, + 0b0111_1110, + 0b0001_1000, + 0b0001_1000, + 0b0011_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::BEL + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0001_1000, + 0b0011_1100, + 0b0011_1100, + 0b0001_1000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::BS + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b1110_0111, + 0b1100_0011, + 0b1100_0011, + 0b1110_0111, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + // Char::HT + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0011_1100, + 0b0110_0110, + 0b0100_0010, + 0b0100_0010, + 0b0110_0110, + 0b0011_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LF + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b1100_0011, + 0b1001_1001, + 0b1011_1101, + 0b1011_1101, + 0b1001_1001, + 0b1100_0011, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + // Char::VT + 0b0000_0000, + 0b0000_0000, + 0b0001_1110, + 0b0000_1110, + 0b0001_1010, + 0b0011_0010, + 0b0111_1000, + 0b1100_1100, + 0b1100_1100, + 0b1100_1100, + 0b1100_1100, + 0b0111_1000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::FF + 0b0000_0000, + 0b0000_0000, + 0b0011_1100, + 0b0110_0110, + 0b0110_0110, + 0b0110_0110, + 0b0110_0110, + 0b0011_1100, + 0b0001_1000, + 0b0111_1110, + 0b0001_1000, + 0b0001_1000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::CR + 0b0000_0000, + 0b0000_0000, + 0b0011_1111, + 0b0011_0011, + 0b0011_1111, + 0b0011_0000, + 0b0011_0000, + 0b0011_0000, + 0b0011_0000, + 0b0111_0000, + 0b1111_0000, + 0b1110_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::SO + 0b0000_0000, + 0b0000_0000, + 0b0111_1111, + 0b0110_0011, + 0b0111_1111, + 0b0110_0011, + 0b0110_0011, + 0b0110_0011, + 0b0110_0011, + 0b0110_0111, + 0b1110_0111, + 0b1110_0110, + 0b1100_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::SI + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0001_1000, + 0b0001_1000, + 0b1101_1011, + 0b0011_1100, + 0b1110_0111, + 0b0011_1100, + 0b1101_1011, + 0b0001_1000, + 0b0001_1000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::DLE + 0b0000_0000, + 0b1000_0000, + 0b1100_0000, + 0b1110_0000, + 0b1111_0000, + 0b1111_1000, + 0b1111_1110, + 0b1111_1000, + 0b1111_0000, + 0b1110_0000, + 0b1100_0000, + 0b1000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::DC1 + 0b0000_0000, + 0b0000_0010, + 0b0000_0110, + 0b0000_1110, + 0b0001_1110, + 0b0011_1110, + 0b1111_1110, + 0b0011_1110, + 0b0001_1110, + 0b0000_1110, + 0b0000_0110, + 0b0000_0010, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::DC2 + 0b0000_0000, + 0b0000_0000, + 0b0001_1000, + 0b0011_1100, + 0b0111_1110, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0111_1110, + 0b0011_1100, + 0b0001_1000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::DC3 + 0b0000_0000, + 0b0000_0000, + 0b0110_0110, + 0b0110_0110, + 0b0110_0110, + 0b0110_0110, + 0b0110_0110, + 0b0110_0110, + 0b0110_0110, + 0b0000_0000, + 0b0110_0110, + 0b0110_0110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::DC4 + 0b0000_0000, + 0b0000_0000, + 0b0111_1111, + 0b1101_1011, + 0b1101_1011, + 0b1101_1011, + 0b0111_1011, + 0b0001_1011, + 0b0001_1011, + 0b0001_1011, + 0b0001_1011, + 0b0001_1011, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::NAK + 0b0000_0000, + 0b0111_1100, + 0b1100_0110, + 0b0110_0000, + 0b0011_1000, + 0b0110_1100, + 0b1100_0110, + 0b1100_0110, + 0b0110_1100, + 0b0011_1000, + 0b0000_1100, + 0b1100_0110, + 0b0111_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::SYN + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b1111_1110, + 0b1111_1110, + 0b1111_1110, + 0b1111_1110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::ETB + 0b0000_0000, + 0b0000_0000, + 0b0001_1000, + 0b0011_1100, + 0b0111_1110, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0111_1110, + 0b0011_1100, + 0b0001_1000, + 0b0111_1110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::CAN + 0b0000_0000, + 0b0000_0000, + 0b0001_1000, + 0b0011_1100, + 0b0111_1110, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::EM + 0b0000_0000, + 0b0000_0000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0111_1110, + 0b0011_1100, + 0b0001_1000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::SUB + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0001_1000, + 0b0000_1100, + 0b1111_1110, + 0b0000_1100, + 0b0001_1000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::Escape + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0011_0000, + 0b0110_0000, + 0b1111_1110, + 0b0110_0000, + 0b0011_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::FS + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b1100_0000, + 0b1100_0000, + 0b1100_0000, + 0b1111_1110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::GS + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0010_1000, + 0b0110_1100, + 0b1111_1110, + 0b0110_1100, + 0b0010_1000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::RS + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0001_0000, + 0b0011_1000, + 0b0011_1000, + 0b0111_1100, + 0b0111_1100, + 0b1111_1110, + 0b1111_1110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::US + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b1111_1110, + 0b1111_1110, + 0b0111_1100, + 0b0111_1100, + 0b0011_1000, + 0b0011_1000, + 0b0001_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::Space + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::ExclamationMark + 0b0000_0000, + 0b0000_0000, + 0b0001_1000, + 0b0011_1100, + 0b0011_1100, + 0b0011_1100, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0000_0000, + 0b0001_1000, + 0b0001_1000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::QuotationMark + 0b0000_0000, + 0b0110_0110, + 0b0110_0110, + 0b0110_0110, + 0b0010_0100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::NumberSign + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0110_1100, + 0b0110_1100, + 0b1111_1110, + 0b0110_1100, + 0b0110_1100, + 0b0110_1100, + 0b1111_1110, + 0b0110_1100, + 0b0110_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::DollarSign + 0b0001_1000, + 0b0001_1000, + 0b0111_1100, + 0b1100_0110, + 0b1100_0010, + 0b1100_0000, + 0b0111_1100, + 0b0000_0110, + 0b0000_0110, + 0b1000_0110, + 0b1100_0110, + 0b0111_1100, + 0b0001_1000, + 0b0001_1000, + 0b0000_0000, + 0b0000_0000, + // Char::PercentSign + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b1100_0010, + 0b1100_0110, + 0b0000_1100, + 0b0001_1000, + 0b0011_0000, + 0b0110_0000, + 0b1100_0110, + 0b1000_0110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::Ampersand + 0b0000_0000, + 0b0000_0000, + 0b0011_1000, + 0b0110_1100, + 0b0110_1100, + 0b0011_1000, + 0b0111_0110, + 0b1101_1100, + 0b1100_1100, + 0b1100_1100, + 0b1100_1100, + 0b0111_0110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::Apostrophe + 0b0000_0000, + 0b0011_0000, + 0b0011_0000, + 0b0011_0000, + 0b0110_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LeftParenthesis + 0b0000_0000, + 0b0000_0000, + 0b0000_1100, + 0b0001_1000, + 0b0011_0000, + 0b0011_0000, + 0b0011_0000, + 0b0011_0000, + 0b0011_0000, + 0b0011_0000, + 0b0001_1000, + 0b0000_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::RightParenthesis + 0b0000_0000, + 0b0000_0000, + 0b0011_0000, + 0b0001_1000, + 0b0000_1100, + 0b0000_1100, + 0b0000_1100, + 0b0000_1100, + 0b0000_1100, + 0b0000_1100, + 0b0001_1000, + 0b0011_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::Asterisk + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0110_0110, + 0b0011_1100, + 0b1111_1111, + 0b0011_1100, + 0b0110_0110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::PlusSign + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0001_1000, + 0b0001_1000, + 0b0111_1110, + 0b0001_1000, + 0b0001_1000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::Comma + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0011_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::HyphenMinus + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b1111_1110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::FullStop + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0001_1000, + 0b0001_1000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::Solidus + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0010, + 0b0000_0110, + 0b0000_1100, + 0b0001_1000, + 0b0011_0000, + 0b0110_0000, + 0b1100_0000, + 0b1000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::DigitZero + 0b0000_0000, + 0b0000_0000, + 0b0011_1000, + 0b0110_1100, + 0b1100_0110, + 0b1100_0110, + 0b1101_0110, + 0b1101_0110, + 0b1100_0110, + 0b1100_0110, + 0b0110_1100, + 0b0011_1000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::DigitOne + 0b0000_0000, + 0b0000_0000, + 0b0001_1000, + 0b0011_1000, + 0b0111_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0111_1110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::DigitTwo + 0b0000_0000, + 0b0000_0000, + 0b0111_1100, + 0b1100_0110, + 0b0000_0110, + 0b0000_1100, + 0b0001_1000, + 0b0011_0000, + 0b0110_0000, + 0b1100_0000, + 0b1100_0110, + 0b1111_1110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::DigitThree + 0b0000_0000, + 0b0000_0000, + 0b0111_1100, + 0b1100_0110, + 0b0000_0110, + 0b0000_0110, + 0b0011_1100, + 0b0000_0110, + 0b0000_0110, + 0b0000_0110, + 0b1100_0110, + 0b0111_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::DigitFour + 0b0000_0000, + 0b0000_0000, + 0b0000_1100, + 0b0001_1100, + 0b0011_1100, + 0b0110_1100, + 0b1100_1100, + 0b1111_1110, + 0b0000_1100, + 0b0000_1100, + 0b0000_1100, + 0b0001_1110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::DigitFive + 0b0000_0000, + 0b0000_0000, + 0b1111_1110, + 0b1100_0000, + 0b1100_0000, + 0b1100_0000, + 0b1111_1100, + 0b0000_0110, + 0b0000_0110, + 0b0000_0110, + 0b1100_0110, + 0b0111_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::DigitSix + 0b0000_0000, + 0b0000_0000, + 0b0011_1000, + 0b0110_0000, + 0b1100_0000, + 0b1100_0000, + 0b1111_1100, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b0111_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::DigitSeven + 0b0000_0000, + 0b0000_0000, + 0b1111_1110, + 0b1100_0110, + 0b0000_0110, + 0b0000_0110, + 0b0000_1100, + 0b0001_1000, + 0b0011_0000, + 0b0011_0000, + 0b0011_0000, + 0b0011_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::DigitEight + 0b0000_0000, + 0b0000_0000, + 0b0111_1100, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b0111_1100, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b0111_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::DigitNine + 0b0000_0000, + 0b0000_0000, + 0b0111_1100, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b0111_1110, + 0b0000_0110, + 0b0000_0110, + 0b0000_0110, + 0b0000_1100, + 0b0111_1000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::Colon + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0001_1000, + 0b0001_1000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0001_1000, + 0b0001_1000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::Semicolon + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0001_1000, + 0b0001_1000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0001_1000, + 0b0001_1000, + 0b0011_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LessThanSign + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0110, + 0b0000_1100, + 0b0001_1000, + 0b0011_0000, + 0b0110_0000, + 0b0011_0000, + 0b0001_1000, + 0b0000_1100, + 0b0000_0110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::EqualsSign + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0111_1110, + 0b0000_0000, + 0b0000_0000, + 0b0111_1110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::GreaterThanSign + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0110_0000, + 0b0011_0000, + 0b0001_1000, + 0b0000_1100, + 0b0000_0110, + 0b0000_1100, + 0b0001_1000, + 0b0011_0000, + 0b0110_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::QuestionMark + 0b0000_0000, + 0b0000_0000, + 0b0111_1100, + 0b1100_0110, + 0b1100_0110, + 0b0000_1100, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0000_0000, + 0b0001_1000, + 0b0001_1000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::CommercialAt + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0111_1100, + 0b1100_0110, + 0b1100_0110, + 0b1101_1110, + 0b1101_1110, + 0b1101_1110, + 0b1101_1100, + 0b1100_0000, + 0b0111_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinCapitalLetterA + 0b0000_0000, + 0b0000_0000, + 0b0001_0000, + 0b0011_1000, + 0b0110_1100, + 0b1100_0110, + 0b1100_0110, + 0b1111_1110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinCapitalLetterB + 0b0000_0000, + 0b0000_0000, + 0b1111_1100, + 0b0110_0110, + 0b0110_0110, + 0b0110_0110, + 0b0111_1100, + 0b0110_0110, + 0b0110_0110, + 0b0110_0110, + 0b0110_0110, + 0b1111_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinCapitalLetterC + 0b0000_0000, + 0b0000_0000, + 0b0011_1100, + 0b0110_0110, + 0b1100_0010, + 0b1100_0000, + 0b1100_0000, + 0b1100_0000, + 0b1100_0000, + 0b1100_0010, + 0b0110_0110, + 0b0011_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinCapitalLetterD + 0b0000_0000, + 0b0000_0000, + 0b1111_1000, + 0b0110_1100, + 0b0110_0110, + 0b0110_0110, + 0b0110_0110, + 0b0110_0110, + 0b0110_0110, + 0b0110_0110, + 0b0110_1100, + 0b1111_1000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinCapitalLetterE + 0b0000_0000, + 0b0000_0000, + 0b1111_1110, + 0b0110_0110, + 0b0110_0010, + 0b0110_1000, + 0b0111_1000, + 0b0110_1000, + 0b0110_0000, + 0b0110_0010, + 0b0110_0110, + 0b1111_1110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinCapitalLetterF + 0b0000_0000, + 0b0000_0000, + 0b1111_1110, + 0b0110_0110, + 0b0110_0010, + 0b0110_1000, + 0b0111_1000, + 0b0110_1000, + 0b0110_0000, + 0b0110_0000, + 0b0110_0000, + 0b1111_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinCapitalLetterG + 0b0000_0000, + 0b0000_0000, + 0b0011_1100, + 0b0110_0110, + 0b1100_0010, + 0b1100_0000, + 0b1100_0000, + 0b1101_1110, + 0b1100_0110, + 0b1100_0110, + 0b0110_0110, + 0b0011_1010, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinCapitalLetterH + 0b0000_0000, + 0b0000_0000, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1111_1110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinCapitalLetterI + 0b0000_0000, + 0b0000_0000, + 0b0011_1100, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0011_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinCapitalLetterJ + 0b0000_0000, + 0b0000_0000, + 0b0001_1110, + 0b0000_1100, + 0b0000_1100, + 0b0000_1100, + 0b0000_1100, + 0b0000_1100, + 0b1100_1100, + 0b1100_1100, + 0b1100_1100, + 0b0111_1000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinCapitalLetterK + 0b0000_0000, + 0b0000_0000, + 0b1110_0110, + 0b0110_0110, + 0b0110_0110, + 0b0110_1100, + 0b0111_1000, + 0b0111_1000, + 0b0110_1100, + 0b0110_0110, + 0b0110_0110, + 0b1110_0110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinCapitalLetterL + 0b0000_0000, + 0b0000_0000, + 0b1111_0000, + 0b0110_0000, + 0b0110_0000, + 0b0110_0000, + 0b0110_0000, + 0b0110_0000, + 0b0110_0000, + 0b0110_0010, + 0b0110_0110, + 0b1111_1110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinCapitalLetterM + 0b0000_0000, + 0b0000_0000, + 0b1100_0110, + 0b1110_1110, + 0b1111_1110, + 0b1111_1110, + 0b1101_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinCapitalLetterN + 0b0000_0000, + 0b0000_0000, + 0b1100_0110, + 0b1110_0110, + 0b1111_0110, + 0b1111_1110, + 0b1101_1110, + 0b1100_1110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinCapitalLetterO + 0b0000_0000, + 0b0000_0000, + 0b0111_1100, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b0111_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinCapitalLetterP + 0b0000_0000, + 0b0000_0000, + 0b1111_1100, + 0b0110_0110, + 0b0110_0110, + 0b0110_0110, + 0b0111_1100, + 0b0110_0000, + 0b0110_0000, + 0b0110_0000, + 0b0110_0000, + 0b1111_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinCapitalLetterQ + 0b0000_0000, + 0b0000_0000, + 0b0111_1100, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1101_0110, + 0b1101_1110, + 0b0111_1100, + 0b0000_1100, + 0b0000_1110, + 0b0000_0000, + 0b0000_0000, + // Char::LatinCapitalLetterR + 0b0000_0000, + 0b0000_0000, + 0b1111_1100, + 0b0110_0110, + 0b0110_0110, + 0b0110_0110, + 0b0111_1100, + 0b0110_1100, + 0b0110_0110, + 0b0110_0110, + 0b0110_0110, + 0b1110_0110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinCapitalLetterS + 0b0000_0000, + 0b0000_0000, + 0b0111_1100, + 0b1100_0110, + 0b1100_0110, + 0b0110_0000, + 0b0011_1000, + 0b0000_1100, + 0b0000_0110, + 0b1100_0110, + 0b1100_0110, + 0b0111_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinCapitalLetterT + 0b0000_0000, + 0b0000_0000, + 0b0111_1110, + 0b0111_1110, + 0b0101_1010, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0011_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinCapitalLetterU + 0b0000_0000, + 0b0000_0000, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b0111_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinCapitalLetterV + 0b0000_0000, + 0b0000_0000, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b0110_1100, + 0b0011_1000, + 0b0001_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinCapitalLetterW + 0b0000_0000, + 0b0000_0000, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1101_0110, + 0b1101_0110, + 0b1101_0110, + 0b1111_1110, + 0b1110_1110, + 0b0110_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinCapitalLetterX + 0b0000_0000, + 0b0000_0000, + 0b1100_0110, + 0b1100_0110, + 0b0110_1100, + 0b0111_1100, + 0b0011_1000, + 0b0011_1000, + 0b0111_1100, + 0b0110_1100, + 0b1100_0110, + 0b1100_0110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinCapitalLetterY + 0b0000_0000, + 0b0000_0000, + 0b0110_0110, + 0b0110_0110, + 0b0110_0110, + 0b0110_0110, + 0b0011_1100, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0011_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinCapitalLetterZ + 0b0000_0000, + 0b0000_0000, + 0b1111_1110, + 0b1100_0110, + 0b1000_0110, + 0b0000_1100, + 0b0001_1000, + 0b0011_0000, + 0b0110_0000, + 0b1100_0010, + 0b1100_0110, + 0b1111_1110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LeftSquareBracket + 0b0000_0000, + 0b0000_0000, + 0b0011_1100, + 0b0011_0000, + 0b0011_0000, + 0b0011_0000, + 0b0011_0000, + 0b0011_0000, + 0b0011_0000, + 0b0011_0000, + 0b0011_0000, + 0b0011_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::ReverseSolidus + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b1000_0000, + 0b1100_0000, + 0b1110_0000, + 0b0111_0000, + 0b0011_1000, + 0b0001_1100, + 0b0000_1110, + 0b0000_0110, + 0b0000_0010, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::RightSquareBracket + 0b0000_0000, + 0b0000_0000, + 0b0011_1100, + 0b0000_1100, + 0b0000_1100, + 0b0000_1100, + 0b0000_1100, + 0b0000_1100, + 0b0000_1100, + 0b0000_1100, + 0b0000_1100, + 0b0011_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::CircumflexAccent + 0b0001_0000, + 0b0011_1000, + 0b0110_1100, + 0b1100_0110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LowLine + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b1111_1111, + 0b0000_0000, + 0b0000_0000, + // Char::GraveAccent + 0b0000_0000, + 0b0011_0000, + 0b0001_1000, + 0b0000_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinSmallLetterA + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0111_1000, + 0b0000_1100, + 0b0111_1100, + 0b1100_1100, + 0b1100_1100, + 0b1100_1100, + 0b0111_0110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinSmallLetterB + 0b0000_0000, + 0b0000_0000, + 0b1110_0000, + 0b0110_0000, + 0b0110_0000, + 0b0111_1000, + 0b0110_1100, + 0b0110_0110, + 0b0110_0110, + 0b0110_0110, + 0b0110_0110, + 0b0111_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinSmallLetterC + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0111_1100, + 0b1100_0110, + 0b1100_0000, + 0b1100_0000, + 0b1100_0000, + 0b1100_0110, + 0b0111_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinSmallLetterD + 0b0000_0000, + 0b0000_0000, + 0b0001_1100, + 0b0000_1100, + 0b0000_1100, + 0b0011_1100, + 0b0110_1100, + 0b1100_1100, + 0b1100_1100, + 0b1100_1100, + 0b1100_1100, + 0b0111_0110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinSmallLetterE + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0111_1100, + 0b1100_0110, + 0b1111_1110, + 0b1100_0000, + 0b1100_0000, + 0b1100_0110, + 0b0111_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinSmallLetterF + 0b0000_0000, + 0b0000_0000, + 0b0001_1100, + 0b0011_0110, + 0b0011_0010, + 0b0011_0000, + 0b0111_1000, + 0b0011_0000, + 0b0011_0000, + 0b0011_0000, + 0b0011_0000, + 0b0111_1000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinSmallLetterG + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0111_0110, + 0b1100_1100, + 0b1100_1100, + 0b1100_1100, + 0b1100_1100, + 0b1100_1100, + 0b0111_1100, + 0b0000_1100, + 0b1100_1100, + 0b0111_1000, + 0b0000_0000, + // Char::LatinSmallLetterH + 0b0000_0000, + 0b0000_0000, + 0b1110_0000, + 0b0110_0000, + 0b0110_0000, + 0b0110_1100, + 0b0111_0110, + 0b0110_0110, + 0b0110_0110, + 0b0110_0110, + 0b0110_0110, + 0b1110_0110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinSmallLetterI + 0b0000_0000, + 0b0000_0000, + 0b0001_1000, + 0b0001_1000, + 0b0000_0000, + 0b0011_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0011_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinSmallLetterJ + 0b0000_0000, + 0b0000_0000, + 0b0000_0110, + 0b0000_0110, + 0b0000_0000, + 0b0000_1110, + 0b0000_0110, + 0b0000_0110, + 0b0000_0110, + 0b0000_0110, + 0b0000_0110, + 0b0000_0110, + 0b0110_0110, + 0b0110_0110, + 0b0011_1100, + 0b0000_0000, + // Char::LatinSmallLetterK + 0b0000_0000, + 0b0000_0000, + 0b1110_0000, + 0b0110_0000, + 0b0110_0000, + 0b0110_0110, + 0b0110_1100, + 0b0111_1000, + 0b0111_1000, + 0b0110_1100, + 0b0110_0110, + 0b1110_0110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinSmallLetterL + 0b0000_0000, + 0b0000_0000, + 0b0011_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0011_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinSmallLetterM + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b1110_1100, + 0b1111_1110, + 0b1101_0110, + 0b1101_0110, + 0b1101_0110, + 0b1101_0110, + 0b1100_0110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinSmallLetterN + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b1101_1100, + 0b0110_0110, + 0b0110_0110, + 0b0110_0110, + 0b0110_0110, + 0b0110_0110, + 0b0110_0110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinSmallLetterO + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0111_1100, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b0111_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinSmallLetterP + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b1101_1100, + 0b0110_0110, + 0b0110_0110, + 0b0110_0110, + 0b0110_0110, + 0b0110_0110, + 0b0111_1100, + 0b0110_0000, + 0b0110_0000, + 0b1111_0000, + 0b0000_0000, + // Char::LatinSmallLetterQ + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0111_0110, + 0b1100_1100, + 0b1100_1100, + 0b1100_1100, + 0b1100_1100, + 0b1100_1100, + 0b0111_1100, + 0b0000_1100, + 0b0000_1100, + 0b0001_1110, + 0b0000_0000, + // Char::LatinSmallLetterR + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b1101_1100, + 0b0111_0110, + 0b0110_0110, + 0b0110_0000, + 0b0110_0000, + 0b0110_0000, + 0b1111_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinSmallLetterS + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0111_1100, + 0b1100_0110, + 0b0110_0000, + 0b0011_1000, + 0b0000_1100, + 0b1100_0110, + 0b0111_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinSmallLetterT + 0b0000_0000, + 0b0000_0000, + 0b0001_0000, + 0b0011_0000, + 0b0011_0000, + 0b1111_1100, + 0b0011_0000, + 0b0011_0000, + 0b0011_0000, + 0b0011_0000, + 0b0011_0110, + 0b0001_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinSmallLetterU + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b1100_1100, + 0b1100_1100, + 0b1100_1100, + 0b1100_1100, + 0b1100_1100, + 0b1100_1100, + 0b0111_0110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinSmallLetterV + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b0110_1100, + 0b0011_1000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinSmallLetterW + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b1100_0110, + 0b1100_0110, + 0b1101_0110, + 0b1101_0110, + 0b1101_0110, + 0b1111_1110, + 0b0110_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinSmallLetterX + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b1100_0110, + 0b0110_1100, + 0b0011_1000, + 0b0011_1000, + 0b0011_1000, + 0b0110_1100, + 0b1100_0110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinSmallLetterY + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b0111_1110, + 0b0000_0110, + 0b0000_1100, + 0b1111_1000, + 0b0000_0000, + // Char::LatinSmallLetterZ + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b1111_1110, + 0b1100_1100, + 0b0001_1000, + 0b0011_0000, + 0b0110_0000, + 0b1100_0110, + 0b1111_1110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LeftCurlyBracket + 0b0000_0000, + 0b0000_0000, + 0b0000_1110, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0111_0000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0000_1110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::VerticalLine + 0b0000_0000, + 0b0000_0000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::RightCurlyBracket + 0b0000_0000, + 0b0000_0000, + 0b0111_0000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0000_1110, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0111_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::Tilde + 0b0000_0000, + 0b0111_0110, + 0b1101_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::Delete + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0001_0000, + 0b0011_1000, + 0b0110_1100, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1111_1110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinCapitalLetterCWithCedilla + 0b0000_0000, + 0b0000_0000, + 0b0011_1100, + 0b0110_0110, + 0b1100_0010, + 0b1100_0000, + 0b1100_0000, + 0b1100_0000, + 0b1100_0000, + 0b1100_0010, + 0b0110_0110, + 0b0011_1100, + 0b0001_1000, + 0b0111_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinSmallLetterUWithDiaeresis + 0b0000_0000, + 0b0000_0000, + 0b1100_1100, + 0b0000_0000, + 0b0000_0000, + 0b1100_1100, + 0b1100_1100, + 0b1100_1100, + 0b1100_1100, + 0b1100_1100, + 0b1100_1100, + 0b0111_0110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinSmallLetterEWithAcute + 0b0000_0000, + 0b0000_1100, + 0b0001_1000, + 0b0011_0000, + 0b0000_0000, + 0b0111_1100, + 0b1100_0110, + 0b1111_1110, + 0b1100_0000, + 0b1100_0000, + 0b1100_0110, + 0b0111_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinSmallLetterAWithCircumflex + 0b0000_0000, + 0b0001_0000, + 0b0011_1000, + 0b0110_1100, + 0b0000_0000, + 0b0111_1000, + 0b0000_1100, + 0b0111_1100, + 0b1100_1100, + 0b1100_1100, + 0b1100_1100, + 0b0111_0110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinSmallLetterAWithDiaeresis + 0b0000_0000, + 0b0000_0000, + 0b1100_1100, + 0b0000_0000, + 0b0000_0000, + 0b0111_1000, + 0b0000_1100, + 0b0111_1100, + 0b1100_1100, + 0b1100_1100, + 0b1100_1100, + 0b0111_0110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinSmallLetterAWithGrave + 0b0000_0000, + 0b0110_0000, + 0b0011_0000, + 0b0001_1000, + 0b0000_0000, + 0b0111_1000, + 0b0000_1100, + 0b0111_1100, + 0b1100_1100, + 0b1100_1100, + 0b1100_1100, + 0b0111_0110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinSmallLetterAWithRingAbove + 0b0000_0000, + 0b0011_1000, + 0b0110_1100, + 0b0011_1000, + 0b0000_0000, + 0b0111_1000, + 0b0000_1100, + 0b0111_1100, + 0b1100_1100, + 0b1100_1100, + 0b1100_1100, + 0b0111_0110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinSmallLetterCWithCedilla + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0111_1100, + 0b1100_0110, + 0b1100_0000, + 0b1100_0000, + 0b1100_0000, + 0b1100_0110, + 0b0111_1100, + 0b0001_1000, + 0b0111_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinSmallLetterEWithCircumflex + 0b0000_0000, + 0b0001_0000, + 0b0011_1000, + 0b0110_1100, + 0b0000_0000, + 0b0111_1100, + 0b1100_0110, + 0b1111_1110, + 0b1100_0000, + 0b1100_0000, + 0b1100_0110, + 0b0111_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinSmallLetterEWithDiaeresis + 0b0000_0000, + 0b0000_0000, + 0b1100_0110, + 0b0000_0000, + 0b0000_0000, + 0b0111_1100, + 0b1100_0110, + 0b1111_1110, + 0b1100_0000, + 0b1100_0000, + 0b1100_0110, + 0b0111_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinSmallLetterEWithGrave + 0b0000_0000, + 0b0110_0000, + 0b0011_0000, + 0b0001_1000, + 0b0000_0000, + 0b0111_1100, + 0b1100_0110, + 0b1111_1110, + 0b1100_0000, + 0b1100_0000, + 0b1100_0110, + 0b0111_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinSmallLetterIWithDiaeresis + 0b0000_0000, + 0b0000_0000, + 0b0110_0110, + 0b0000_0000, + 0b0000_0000, + 0b0011_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0011_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinSmallLetterIWithCircumflex + 0b0000_0000, + 0b0001_1000, + 0b0011_1100, + 0b0110_0110, + 0b0000_0000, + 0b0011_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0011_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinSmallLetterIWithGrave + 0b0000_0000, + 0b0110_0000, + 0b0011_0000, + 0b0001_1000, + 0b0000_0000, + 0b0011_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0011_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinCapitalLetterAWithDiaeresis + 0b0000_0000, + 0b1100_0110, + 0b0000_0000, + 0b0001_0000, + 0b0011_1000, + 0b0110_1100, + 0b1100_0110, + 0b1100_0110, + 0b1111_1110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinCapitalLetterAWithRingAbove + 0b0011_1000, + 0b0110_1100, + 0b0011_1000, + 0b0001_0000, + 0b0011_1000, + 0b0110_1100, + 0b1100_0110, + 0b1100_0110, + 0b1111_1110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinCapitalLetterEWithAcute + 0b0000_1100, + 0b0001_1000, + 0b0000_0000, + 0b1111_1110, + 0b0110_0110, + 0b0110_0010, + 0b0110_1000, + 0b0111_1000, + 0b0110_1000, + 0b0110_0010, + 0b0110_0110, + 0b1111_1110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinSmallLetterAe + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b1110_1100, + 0b0011_0110, + 0b0011_0110, + 0b0111_1110, + 0b1101_1000, + 0b1101_1000, + 0b0110_1110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinCapitalLetterAe + 0b0000_0000, + 0b0000_0000, + 0b0011_1110, + 0b0110_1100, + 0b1100_1100, + 0b1100_1100, + 0b1111_1110, + 0b1100_1100, + 0b1100_1100, + 0b1100_1100, + 0b1100_1100, + 0b1100_1110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinSmallLetterOWithCircumflex + 0b0000_0000, + 0b0001_0000, + 0b0011_1000, + 0b0110_1100, + 0b0000_0000, + 0b0111_1100, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b0111_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinSmallLetterOWithDiaeresis + 0b0000_0000, + 0b0000_0000, + 0b1100_0110, + 0b0000_0000, + 0b0000_0000, + 0b0111_1100, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b0111_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinSmallLetterOWithGrave + 0b0000_0000, + 0b0110_0000, + 0b0011_0000, + 0b0001_1000, + 0b0000_0000, + 0b0111_1100, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b0111_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinSmallLetterUWithCircumflex + 0b0000_0000, + 0b0011_0000, + 0b0111_1000, + 0b1100_1100, + 0b0000_0000, + 0b1100_1100, + 0b1100_1100, + 0b1100_1100, + 0b1100_1100, + 0b1100_1100, + 0b1100_1100, + 0b0111_0110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinSmallLetterUWithGrave + 0b0000_0000, + 0b0110_0000, + 0b0011_0000, + 0b0001_1000, + 0b0000_0000, + 0b1100_1100, + 0b1100_1100, + 0b1100_1100, + 0b1100_1100, + 0b1100_1100, + 0b1100_1100, + 0b0111_0110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinSmallLetterYWithDiaeresis + 0b0000_0000, + 0b0000_0000, + 0b1100_0110, + 0b0000_0000, + 0b0000_0000, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b0111_1110, + 0b0000_0110, + 0b0000_1100, + 0b0111_1000, + 0b0000_0000, + // Char::LatinCapitalLetterOWithDiaeresis + 0b0000_0000, + 0b1100_0110, + 0b0000_0000, + 0b0111_1100, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b0111_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinCapitalLetterUWithDiaeresis + 0b0000_0000, + 0b1100_0110, + 0b0000_0000, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b0111_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinSmallLetterOWithStroke + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0111_1100, + 0b1100_1110, + 0b1101_1110, + 0b1111_0110, + 0b1110_0110, + 0b1100_0110, + 0b0111_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::PoundSign + 0b0000_0000, + 0b0011_1000, + 0b0110_1100, + 0b0110_0100, + 0b0110_0000, + 0b1111_0000, + 0b0110_0000, + 0b0110_0000, + 0b0110_0000, + 0b0110_0000, + 0b1110_0110, + 0b1111_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinCapitalLetterOWithStroke + 0b0000_0000, + 0b0000_0100, + 0b0111_1100, + 0b1100_1110, + 0b1100_1110, + 0b1101_0110, + 0b1101_0110, + 0b1101_0110, + 0b1101_0110, + 0b1110_0110, + 0b1110_0110, + 0b0111_1100, + 0b0100_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::MultiplicationSign + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b1100_0110, + 0b0110_1100, + 0b0011_1000, + 0b0011_1000, + 0b0110_1100, + 0b1100_0110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinSmallLetterFWithHook + 0b0000_0000, + 0b0000_1110, + 0b0001_1011, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0111_1110, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b1101_1000, + 0b0111_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinSmallLetterAWithAcute + 0b0000_0000, + 0b0001_1000, + 0b0011_0000, + 0b0110_0000, + 0b0000_0000, + 0b0111_1000, + 0b0000_1100, + 0b0111_1100, + 0b1100_1100, + 0b1100_1100, + 0b1100_1100, + 0b0111_0110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinSmallLetterIWithAcute + 0b0000_0000, + 0b0000_1100, + 0b0001_1000, + 0b0011_0000, + 0b0000_0000, + 0b0011_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0011_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinSmallLetterOWithAcute + 0b0000_0000, + 0b0001_1000, + 0b0011_0000, + 0b0110_0000, + 0b0000_0000, + 0b0111_1100, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b0111_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinSmallLetterUWithAcute + 0b0000_0000, + 0b0001_1000, + 0b0011_0000, + 0b0110_0000, + 0b0000_0000, + 0b1100_1100, + 0b1100_1100, + 0b1100_1100, + 0b1100_1100, + 0b1100_1100, + 0b1100_1100, + 0b0111_0110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinSmallLetterNWithTilde + 0b0000_0000, + 0b0000_0000, + 0b0111_0110, + 0b1101_1100, + 0b0000_0000, + 0b1101_1100, + 0b0110_0110, + 0b0110_0110, + 0b0110_0110, + 0b0110_0110, + 0b0110_0110, + 0b0110_0110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinCapitalLetterNWithTilde + 0b0111_0110, + 0b1101_1100, + 0b0000_0000, + 0b1100_0110, + 0b1110_0110, + 0b1111_0110, + 0b1111_1110, + 0b1101_1110, + 0b1100_1110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::FeminineOrdinalIndicator + 0b0000_0000, + 0b0000_0000, + 0b0011_1100, + 0b0110_1100, + 0b0110_1100, + 0b0011_1110, + 0b0000_0000, + 0b0111_1110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::MasculineOrdinalIndicator + 0b0000_0000, + 0b0000_0000, + 0b0011_1000, + 0b0110_1100, + 0b0110_1100, + 0b0011_1000, + 0b0000_0000, + 0b0111_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::InvertedQuestionMark + 0b0000_0000, + 0b0000_0000, + 0b0011_0000, + 0b0011_0000, + 0b0000_0000, + 0b0011_0000, + 0b0011_0000, + 0b0110_0000, + 0b1100_0000, + 0b1100_0110, + 0b1100_0110, + 0b0111_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::RegisteredSign + 0b0000_0000, + 0b0000_0000, + 0b0111_1100, + 0b1000_0010, + 0b1011_0010, + 0b1010_1010, + 0b1011_0010, + 0b1010_1010, + 0b1010_1010, + 0b1000_0010, + 0b0111_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::NotSign + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b1111_1110, + 0b0000_0110, + 0b0000_0110, + 0b0000_0110, + 0b0000_0110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::VulgarFractionOneHalf + 0b0000_0000, + 0b0110_0000, + 0b1110_0000, + 0b0110_0010, + 0b0110_0110, + 0b0110_1100, + 0b0001_1000, + 0b0011_0000, + 0b0110_0000, + 0b1101_1100, + 0b1000_0110, + 0b0000_1100, + 0b0001_1000, + 0b0011_1110, + 0b0000_0000, + 0b0000_0000, + // Char::VulgarFractionOneQuarter + 0b0000_0000, + 0b0110_0000, + 0b1110_0000, + 0b0110_0010, + 0b0110_0110, + 0b0110_1100, + 0b0001_1000, + 0b0011_0000, + 0b0110_0110, + 0b1100_1110, + 0b1001_1010, + 0b0011_1111, + 0b0000_0110, + 0b0000_0110, + 0b0000_0000, + 0b0000_0000, + // Char::InvertedExclamationMark + 0b0000_0000, + 0b0000_0000, + 0b0001_1000, + 0b0001_1000, + 0b0000_0000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0011_1100, + 0b0011_1100, + 0b0011_1100, + 0b0001_1000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LeftPointingDoubleAngleQuotationMark + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0011_0110, + 0b0110_1100, + 0b1101_1000, + 0b0110_1100, + 0b0011_0110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::RightPointingDoubleAngleQuotationMark + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b1101_1000, + 0b0110_1100, + 0b0011_0110, + 0b0110_1100, + 0b1101_1000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LightShade + 0b0001_0001, + 0b0100_0100, + 0b0001_0001, + 0b0100_0100, + 0b0001_0001, + 0b0100_0100, + 0b0001_0001, + 0b0100_0100, + 0b0001_0001, + 0b0100_0100, + 0b0001_0001, + 0b0100_0100, + 0b0001_0001, + 0b0100_0100, + 0b0001_0001, + 0b0100_0100, + // Char::MediumShade + 0b0101_0101, + 0b1010_1010, + 0b0101_0101, + 0b1010_1010, + 0b0101_0101, + 0b1010_1010, + 0b0101_0101, + 0b1010_1010, + 0b0101_0101, + 0b1010_1010, + 0b0101_0101, + 0b1010_1010, + 0b0101_0101, + 0b1010_1010, + 0b0101_0101, + 0b1010_1010, + // Char::DarkShade + 0b1101_1101, + 0b0111_0111, + 0b1101_1101, + 0b0111_0111, + 0b1101_1101, + 0b0111_0111, + 0b1101_1101, + 0b0111_0111, + 0b1101_1101, + 0b0111_0111, + 0b1101_1101, + 0b0111_0111, + 0b1101_1101, + 0b0111_0111, + 0b1101_1101, + 0b0111_0111, + // Char::BoxDrawingsLightVertical + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + // Char::BoxDrawingsLightVerticalAndLeft + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b1111_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + // Char::LatinCapitalLetterAWithAcute + 0b0110_0000, + 0b1100_0000, + 0b0001_0000, + 0b0011_1000, + 0b0110_1100, + 0b1100_0110, + 0b1100_0110, + 0b1111_1110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinCapitalLetterAWithCircumflex + 0b0111_1100, + 0b1100_0110, + 0b0001_0000, + 0b0011_1000, + 0b0110_1100, + 0b1100_0110, + 0b1100_0110, + 0b1111_1110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinCapitalLetterAWithGrave + 0b0000_1100, + 0b0000_0110, + 0b0001_0000, + 0b0011_1000, + 0b0110_1100, + 0b1100_0110, + 0b1100_0110, + 0b1111_1110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::CopyrightSign + 0b0000_0000, + 0b0000_0000, + 0b0111_1100, + 0b1000_0010, + 0b1001_1010, + 0b1010_0010, + 0b1010_0010, + 0b1010_0010, + 0b1001_1010, + 0b1000_0010, + 0b0111_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::BoxDrawingsDoubleVerticalAndLeft + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b1111_0110, + 0b0000_0110, + 0b1111_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + // Char::BoxDrawingsDoubleVertical + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + // Char::BoxDrawingsDoubleDownAndLeft + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b1111_1110, + 0b0000_0110, + 0b1111_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + // Char::BoxDrawingsDoubleUpAndLeft + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b1111_0110, + 0b0000_0110, + 0b1111_1110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::CentSign + 0b0000_0000, + 0b0000_0000, + 0b0001_1000, + 0b0001_1000, + 0b0111_1100, + 0b1100_0110, + 0b1100_0000, + 0b1100_0000, + 0b1100_0110, + 0b0111_1100, + 0b0001_1000, + 0b0001_1000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::YenSign + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0110_0110, + 0b0110_0110, + 0b0011_1100, + 0b0001_1000, + 0b0111_1110, + 0b0001_1000, + 0b0111_1110, + 0b0001_1000, + 0b0001_1000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::BoxDrawingsLightDownAndLeft + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b1111_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + // Char::BoxDrawingsLightUpAndRight + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1111, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::BoxDrawingsLightUpAndHorizontal + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b1111_1111, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::BoxDrawingsLightDownAndHorizontal + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b1111_1111, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + // Char::BoxDrawingsLightVerticalAndRight + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1111, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + // Char::BoxDrawingsLightHorizontal + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b1111_1111, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::BoxDrawingsLightVerticalAndHorizontal + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b1111_1111, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + // Char::LatinSmallLetterAWithTilde + 0b0000_0000, + 0b0000_0000, + 0b0111_0110, + 0b1101_1100, + 0b0000_0000, + 0b0111_1000, + 0b0000_1100, + 0b0111_1100, + 0b1100_1100, + 0b1100_1100, + 0b1100_1100, + 0b0111_0110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinCapitalLetterAWithTilde + 0b0111_0110, + 0b1101_1100, + 0b0000_0000, + 0b0011_1000, + 0b0110_1100, + 0b1100_0110, + 0b1100_0110, + 0b1111_1110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::BoxDrawingsDoubleUpAndRight + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0111, + 0b0011_0000, + 0b0011_1111, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::BoxDrawingsDoubleDownAndRight + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0011_1111, + 0b0011_0000, + 0b0011_0111, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + // Char::BoxDrawingsDoubleUpAndHorizontal + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b1111_0111, + 0b0000_0000, + 0b1111_1111, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::BoxDrawingsDoubleDownAndHorizontal + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b1111_1111, + 0b0000_0000, + 0b1111_0111, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + // Char::BoxDrawingsDoubleVerticalAndRight + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0111, + 0b0011_0000, + 0b0011_0111, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + // Char::BoxDrawingsDoubleHorizontal + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b1111_1111, + 0b0000_0000, + 0b1111_1111, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::BoxDrawingsDoubleVerticalAndHorizontal + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b1111_0111, + 0b0000_0000, + 0b1111_0111, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + 0b0011_0110, + // Char::CurrencySign + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b1100_0110, + 0b0111_1100, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b0111_1100, + 0b1100_0110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinSmallLetterEth + 0b0000_0000, + 0b0000_0000, + 0b0011_0100, + 0b0001_1000, + 0b0010_1100, + 0b0000_0110, + 0b0011_1110, + 0b0110_0110, + 0b0110_0110, + 0b0110_0110, + 0b0110_0110, + 0b0011_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinCapitalLetterEth + 0b0000_0000, + 0b0000_0000, + 0b1111_1000, + 0b0110_1100, + 0b0110_0110, + 0b0110_0110, + 0b1111_0110, + 0b0110_0110, + 0b0110_0110, + 0b0110_0110, + 0b0110_1100, + 0b1111_1000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinCapitalLetterEWithCircumflex + 0b0011_1000, + 0b0110_1100, + 0b0000_0000, + 0b1111_1110, + 0b0110_0110, + 0b0110_0010, + 0b0110_1000, + 0b0111_1000, + 0b0110_1000, + 0b0110_0010, + 0b0110_0110, + 0b1111_1110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinCapitalLetterEWithDiaeresis + 0b0000_0000, + 0b1100_0110, + 0b0000_0000, + 0b1111_1110, + 0b0110_0110, + 0b0110_0010, + 0b0110_1000, + 0b0111_1000, + 0b0110_1000, + 0b0110_0010, + 0b0110_0110, + 0b1111_1110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinCapitalLetterEWithGrave + 0b0011_0000, + 0b0001_1000, + 0b0000_0000, + 0b1111_1110, + 0b0110_0110, + 0b0110_0010, + 0b0110_1000, + 0b0111_1000, + 0b0110_1000, + 0b0110_0010, + 0b0110_0110, + 0b1111_1110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinSmallLetterDotlessI + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0011_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0011_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinCapitalLetterIWithAcute + 0b0000_1100, + 0b0001_1000, + 0b0000_0000, + 0b0011_1100, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0011_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinCapitalLetterIWithCircumflex + 0b0011_1100, + 0b0110_0110, + 0b0000_0000, + 0b0011_1100, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0011_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinCapitalLetterIWithDiaeresis + 0b0000_0000, + 0b0110_0110, + 0b0000_0000, + 0b0011_1100, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0011_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::BoxDrawingsLightUpAndLeft + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b1111_1000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::BoxDrawingsLightDownAndRight + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0001_1111, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + // Char::FullBlock + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + // Char::LowerHalfBlock + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + // Char::BrokenBar + 0b0000_0000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0000_0000, + 0b0000_0000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinCapitalLetterIWithGrave + 0b0011_0000, + 0b0001_1000, + 0b0000_0000, + 0b0011_1100, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0011_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::UpperHalfBlock + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinCapitalLetterOWithAcute + 0b0001_1000, + 0b0011_0000, + 0b0000_0000, + 0b0111_1100, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b0111_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinSmallLetterSharpS + 0b0000_0000, + 0b0000_0000, + 0b0111_1000, + 0b1100_1100, + 0b1100_1100, + 0b1100_1100, + 0b1101_1000, + 0b1100_1100, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinCapitalLetterOWithCircumflex + 0b0011_1000, + 0b0110_1100, + 0b0000_0000, + 0b0111_1100, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b0111_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinCapitalLetterOWithGrave + 0b0011_0000, + 0b0001_1000, + 0b0000_0000, + 0b0111_1100, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b0111_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinSmallLetterOWithTilde + 0b0000_0000, + 0b0000_0000, + 0b0111_0110, + 0b1101_1100, + 0b0000_0000, + 0b0111_1100, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b0111_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinCapitalLetterOWithTilde + 0b0111_0110, + 0b1101_1100, + 0b0000_0000, + 0b0111_1100, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b0111_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::MicroSign + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0110_0110, + 0b0110_0110, + 0b0110_0110, + 0b0110_0110, + 0b0110_0110, + 0b0110_0110, + 0b0111_1100, + 0b0110_0000, + 0b0110_0000, + 0b1100_0000, + 0b0000_0000, + // Char::LatinSmallLetterThorn + 0b0000_0000, + 0b0000_0000, + 0b1110_0000, + 0b0110_0000, + 0b0110_0000, + 0b0111_1100, + 0b0110_0110, + 0b0110_0110, + 0b0110_0110, + 0b0110_0110, + 0b0110_0110, + 0b0111_1100, + 0b0110_0000, + 0b0110_0000, + 0b1111_0000, + 0b0000_0000, + // Char::LatinCapitalLetterThorn + 0b0000_0000, + 0b0000_0000, + 0b1111_0000, + 0b0110_0000, + 0b0111_1100, + 0b0110_0110, + 0b0110_0110, + 0b0110_0110, + 0b0110_0110, + 0b0111_1100, + 0b0110_0000, + 0b1111_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinCapitalLetterUWithAcute + 0b0001_1000, + 0b0011_0000, + 0b0000_0000, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b0111_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinCapitalLetterUWithCircumflex + 0b0011_1000, + 0b0110_1100, + 0b0000_0000, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b0111_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinCapitalLetterUWithGrave + 0b0011_0000, + 0b0001_1000, + 0b0000_0000, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b0111_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::LatinSmallLetterYWithAcute + 0b0000_0000, + 0b0000_1100, + 0b0001_1000, + 0b0011_0000, + 0b0000_0000, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b1100_0110, + 0b0111_1110, + 0b0000_0110, + 0b0000_1100, + 0b1111_1000, + 0b0000_0000, + // Char::LatinCapitalLetterYWithAcute + 0b0000_1100, + 0b0001_1000, + 0b0000_0000, + 0b0110_0110, + 0b0110_0110, + 0b0110_0110, + 0b0110_0110, + 0b0011_1100, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0011_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::Macron + 0b0000_0000, + 0b1111_1111, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::AcuteAccent + 0b0000_0000, + 0b0000_1100, + 0b0001_1000, + 0b0011_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::SoftHyphen + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b1111_1110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::PlusMinusSign + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0001_1000, + 0b0001_1000, + 0b0111_1110, + 0b0001_1000, + 0b0001_1000, + 0b0000_0000, + 0b0000_0000, + 0b0111_1110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::DoubleLowLine + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b1111_1111, + 0b0000_0000, + 0b1111_1111, + 0b0000_0000, + // Char::VulgarFractionThreeQuarters + 0b0000_0000, + 0b1110_0000, + 0b0011_0000, + 0b0110_0010, + 0b0011_0110, + 0b1110_1100, + 0b0001_1000, + 0b0011_0000, + 0b0110_0110, + 0b1100_1110, + 0b1001_1010, + 0b0011_1111, + 0b0000_0110, + 0b0000_0110, + 0b0000_0000, + 0b0000_0000, + // Char::PilcrowSign + 0b0000_0000, + 0b0000_0000, + 0b0111_1111, + 0b1101_1011, + 0b1101_1011, + 0b1101_1011, + 0b0111_1011, + 0b0001_1011, + 0b0001_1011, + 0b0001_1011, + 0b0001_1011, + 0b0001_1011, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::SectionSign + 0b0000_0000, + 0b0111_1100, + 0b1100_0110, + 0b0110_0000, + 0b0011_1000, + 0b0110_1100, + 0b1100_0110, + 0b1100_0110, + 0b0110_1100, + 0b0011_1000, + 0b0000_1100, + 0b1100_0110, + 0b0111_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::DivisionSign + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0001_1000, + 0b0000_0000, + 0b0111_1110, + 0b0000_0000, + 0b0001_1000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::Cedilla + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0001_1000, + 0b0000_1100, + 0b0111_1000, + 0b0000_0000, + 0b0000_0000, + // Char::DegreeSign + 0b0000_0000, + 0b0011_1000, + 0b0110_1100, + 0b0110_1100, + 0b0011_1000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::Diaeresis + 0b0000_0000, + 0b1100_0110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::MiddleDot + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0001_1000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::SuperscriptOne + 0b0000_0000, + 0b0001_1000, + 0b0011_1000, + 0b0001_1000, + 0b0001_1000, + 0b0001_1000, + 0b0011_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::SuperscriptThree + 0b0000_0000, + 0b0111_1100, + 0b0000_0110, + 0b0011_1100, + 0b0000_0110, + 0b0000_0110, + 0b0111_1100, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::SuperscriptTwo + 0b0000_0000, + 0b0011_1100, + 0b0110_0110, + 0b0000_1100, + 0b0001_1000, + 0b0011_0010, + 0b0111_1110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::BlackSquare + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0111_1110, + 0b0111_1110, + 0b0111_1110, + 0b0111_1110, + 0b0111_1110, + 0b0111_1110, + 0b0111_1110, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + // Char::NoBreakSpace + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, + 0b0000_0000, +]; + +// End of file diff --git a/src/main.rs b/src/main.rs index 8fc85dd..fd0760e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -95,7 +95,6 @@ struct LineBuffer { pixels: [u32; NUM_PIXEL_PAIRS_PER_LINE + 1], } - static mut CHAR_ARRAY: [u8; 2000] = *b"\ \xDA\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xBF\ @@ -568,7 +567,7 @@ fn main() -> ! { let text_row_offset = text_row * 80; if text_row < 25 { - let row_slice = unsafe { &CHAR_ARRAY[text_row_offset..text_row_offset+80] }; + let row_slice = unsafe { &CHAR_ARRAY[text_row_offset..text_row_offset + 80] }; let font_ptr = unsafe { freebsd_cp850::FONT_DATA.as_ptr().add(font_row) }; let px_ptr = px_buf.pixels[1..].as_mut_ptr(); @@ -578,10 +577,22 @@ fn main() -> ! { let mono_pixels = unsafe { *font_ptr.offset(index) } as usize; // Convert from mono to RGB white unsafe { - core::ptr::write_volatile(px_ptr.offset(px_idx + 0), LOOKUP[(mono_pixels >> 6) & 3]); - core::ptr::write_volatile(px_ptr.offset(px_idx + 1), LOOKUP[(mono_pixels >> 4) & 3]); - core::ptr::write_volatile(px_ptr.offset(px_idx + 2), LOOKUP[(mono_pixels >> 2) & 3]); - core::ptr::write_volatile(px_ptr.offset(px_idx + 3), LOOKUP[(mono_pixels >> 0) & 3]); + core::ptr::write_volatile( + px_ptr.offset(px_idx + 0), + LOOKUP[(mono_pixels >> 6) & 3], + ); + core::ptr::write_volatile( + px_ptr.offset(px_idx + 1), + LOOKUP[(mono_pixels >> 4) & 3], + ); + core::ptr::write_volatile( + px_ptr.offset(px_idx + 2), + LOOKUP[(mono_pixels >> 2) & 3], + ); + core::ptr::write_volatile( + px_ptr.offset(px_idx + 3), + LOOKUP[(mono_pixels >> 0) & 3], + ); } px_idx += 4; } @@ -590,7 +601,7 @@ fn main() -> ! { } } -static LOOKUP: [u32; 4] = [ 0x0F00_0F00, 0x00FF_0F00, 0x0F00_00FF, 0x00FF_00FF ]; +static LOOKUP: [u32; 4] = [0x0F00_0F00, 0x00FF_0F00, 0x0F00_00FF, 0x00FF_00FF]; const fn make_timing(period: u32, hsync: bool, vsync: bool, raise_irq: bool) -> u32 { let command = if raise_irq { From 6468f32ac12f648517305e0d68eb931ab6a0e00a Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Thu, 18 Nov 2021 14:22:57 +0000 Subject: [PATCH 27/41] Cleaning up the code. Video stuff is now in its own module. --- src/main.rs | 566 +------------------ src/{freebsd_cp850.rs => vga/font.rs} | 69 ++- src/vga/mod.rs | 770 ++++++++++++++++++++++++++ 3 files changed, 830 insertions(+), 575 deletions(-) rename src/{freebsd_cp850.rs => vga/font.rs} (97%) create mode 100644 src/vga/mod.rs diff --git a/src/main.rs b/src/main.rs index fd0760e..60920f5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -33,95 +33,36 @@ #![no_std] #![no_main] +// ----------------------------------------------------------------------------- +// Sub-modules +// ----------------------------------------------------------------------------- + +pub mod vga; + // ----------------------------------------------------------------------------- // Imports // ----------------------------------------------------------------------------- -use core::sync::atomic::{AtomicBool, AtomicU16, Ordering}; use cortex_m_rt::entry; use defmt::*; use defmt_rtt as _; use embedded_hal::digital::v2::OutputPin; use embedded_time::rate::*; use git_version::git_version; -use hal::clocks::Clock; use panic_probe as _; use pico::{ self, hal::{ self, pac::{self, interrupt}, - pio::{self as hal_pio, PIOExt}, }, }; -mod freebsd_cp850; - // ----------------------------------------------------------------------------- // Types // ----------------------------------------------------------------------------- -#[repr(C, align(16))] -struct ScanlineTimingBuffer { - data: [u32; 4], -} - -#[repr(C, align(16))] -struct TimingBuffer { - visible_line: ScanlineTimingBuffer, - vblank_porch_buffer: ScanlineTimingBuffer, - vblank_sync_buffer: ScanlineTimingBuffer, -} - -/// How many pixels per scan-line. Adjust the pixel PIO program to run at the -/// right speed to the screen is filled. For example, if this is only 320 but -/// you are aiming at 640x480, make the pixel PIO take twice as long per -/// pixel. -const NUM_PIXELS_PER_LINE: usize = 640; - -/// How many pixel pairs we send out. Each pixel is two 12-bit values packed -/// into one 32-bit word. This is to make more efficient use of DMA and FIFO -/// resources. -const NUM_PIXEL_PAIRS_PER_LINE: usize = NUM_PIXELS_PER_LINE / 2; - -/// Number of lines on screen. -const NUM_LINES: u16 = 400; - -/// Index of the last line -const LAST_LINE: u16 = NUM_LINES - 1; - -#[repr(C, align(16))] -struct LineBuffer { - pixels: [u32; NUM_PIXEL_PAIRS_PER_LINE + 1], -} - -static mut CHAR_ARRAY: [u8; 2000] = -*b"\ -\xDA\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xBF\ -\xB3Neotron Pico Booting \xB3\ -\xB3 \xB3\ -\xB3 \xB3\ -\xB3 \xB3\ -\xB3 \xB3\ -\xB3 \xDb\xDb\xDb\xBB\xB2\xB2\xDb\xDb\xBB\xDb\xDb\xDb\xDb\xDb\xDb\xDb\xBB\xB2\xDb\xDb\xDb\xDb\xDb\xBB\xB2\xDb\xDb\xDb\xDb\xDb\xDb\xDb\xDb\xBB\xDb\xDb\xDb\xDb\xDb\xDb\xBB\xB2\xB2\xDb\xDb\xDb\xDb\xDb\xBB\xB2\xDb\xDb\xDb\xBB\xB2\xB2\xDb\xDb\xBB \xB3\ -\xB3 \xDb\xDb\xDb\xDb\xBB\xB2\xDb\xDb\xBA\xDb\xDb\xC9\xCD\xCD\xCD\xCD\xBC\xDb\xDb\xC9\xCD\xCD\xDb\xDb\xBB\xC8\xCD\xCD\xDb\xDb\xC9\xCD\xCD\xBC\xDb\xDb\xC9\xCD\xCD\xDb\xDb\xBB\xDb\xDb\xC9\xCD\xCD\xDb\xDb\xBB\xDb\xDb\xDb\xDb\xBB\xB2\xDb\xDb\xBA \xB3\ -\xB3 \xDb\xDb\xC9\xDb\xDb\xBB\xDb\xDb\xBA\xDb\xDb\xDb\xDb\xDb\xBB\xB2\xB2\xDb\xDb\xBA\xB2\xB2\xDb\xDb\xBA\xB2\xB2\xB2\xDb\xDb\xBA\xB2\xB2\xB2\xDb\xDb\xDb\xDb\xDb\xDb\xC9\xBC\xDb\xDb\xBA\xB2\xB2\xDb\xDb\xBA\xDb\xDb\xC9\xDb\xDb\xBB\xDb\xDb\xBA \xB3\ -\xB3 \xDb\xDb\xBA\xC8\xDb\xDb\xDb\xDb\xBA\xDb\xDb\xC9\xCD\xCD\xBC\xB2\xB2\xDb\xDb\xBA\xB2\xB2\xDb\xDb\xBA\xB2\xB2\xB2\xDb\xDb\xBA\xB2\xB2\xB2\xDb\xDb\xC9\xCD\xCD\xDb\xDb\xBB\xDb\xDb\xBA\xB2\xB2\xDb\xDb\xBA\xDb\xDb\xBA\xC8\xDb\xDb\xDb\xDb\xBA \xB3\ -\xB3 \xDb\xDb\xBA\xB2\xC8\xDb\xDb\xDb\xBA\xDb\xDb\xDb\xDb\xDb\xDb\xDb\xBB\xC8\xDb\xDb\xDb\xDb\xDb\xC9\xBC\xB2\xB2\xB2\xDb\xDb\xBA\xB2\xB2\xB2\xDb\xDb\xBA\xB2\xB2\xDb\xDb\xBA\xC8\xDb\xDb\xDb\xDb\xDb\xC9\xBC\xDb\xDb\xBA\xB2\xC8\xDb\xDb\xDb\xBA \xB3\ -\xB3 \xB3\ -\xB3 \xB3\ -\xB3 \xB3\ -\xB3 \xB3\ -\xB3 \xB3\ -\xB3 \xB3\ -\xB3 \xB3\ -\xB3 \xB3\ -\xB3 \xB3\ -\xB3 \xB3\ -\xB3 \xB3\ -\xB3 \xB3\ -\xB3This is the end \xB3\ -\xC0\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xD9"; +// None // ----------------------------------------------------------------------------- // Static and Const Data @@ -140,84 +81,6 @@ pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER; /// BIOS version const GIT_VERSION: &str = git_version!(); -/// Stores our timing data which we DMA into the timing PIO State Machine -static TIMING_BUFFER: TimingBuffer = TimingBuffer { - // Note - the order of the arguments to `make_timing` is: - // - // * H-Sync Pulse (true = high, false = low) - // * V-Sync Pulse (true = high, false = low) - // * Generate pixel start IRQ (true or false) - visible_line: ScanlineTimingBuffer { - data: [ - // Front porch (as per the spec) - make_timing(16 * 5, true, false, false), - // Sync pulse (as per the spec) - make_timing(96 * 5, false, false, false), - // Back porch (shortened by two pixels because the - // video starts two pixels late) - make_timing(48 * 5, true, false, false), - // Visible portion. It also triggers the IRQ to start pixels moving. - make_timing(640 * 5, true, false, true), - ], - }, - vblank_porch_buffer: ScanlineTimingBuffer { - data: [ - // Front porch - make_timing(16 * 5, true, false, false), - // Sync pulse - make_timing(96 * 5, false, false, false), - // Back porch - make_timing(48 * 5, true, false, false), - // 'visible' portion (but it's blank) - make_timing(640 * 5, true, false, false), - ], - }, - vblank_sync_buffer: ScanlineTimingBuffer { - data: [ - // Front porch - make_timing(16 * 5, true, true, false), - // Sync pulse - make_timing(96 * 5, false, true, false), - // Back porch - make_timing(48 * 5, true, true, false), - // Visible portion (but it's blank) - make_timing(640 * 5, true, true, false), - ], - }, -}; - -/// Tracks which scanline we are currently on (for timing purposes => it goes 0..=449) -static CURRENT_TIMING_LINE: AtomicU16 = AtomicU16::new(0); - -/// Tracks which scanline we are currently on (for pixel purposes => it goes 0..NUM_LINES) -static CURRENT_DISPLAY_LINE: AtomicU16 = AtomicU16::new(0); - -/// Set to `true` when DMA of previous line is complete and next line is scheduled. -static DMA_READY: AtomicBool = AtomicBool::new(false); - -/// Somewhere to stash the DMA controller object, so the IRQ can find it -static mut DMA_PERIPH: Option = None; - -/// DMA channel for the timing FIFO -static mut TIMING_DMA_CHAN: usize = 0; - -/// DMA channel for the pixel FIFO -static mut PIXEL_DMA_CHAN: usize = 1; - -/// 12-bit pixels for the even scan-lines (0, 2, 4 ... NUM_LINES - 2). Defaults to black. -static mut PIXEL_DATA_BUFFER_EVEN: LineBuffer = LineBuffer { - // The length is one less than the number of pixel pairs because of the - // way the jmp works in the PIO program (it's a do-while loop). - pixels: [0; NUM_PIXEL_PAIRS_PER_LINE + 1], -}; - -/// 12-bit pixels for the odd scan-lines (1, 3, 5 ... NUM_LINES-1). Defaults to white. -static mut PIXEL_DATA_BUFFER_ODD: LineBuffer = LineBuffer { - // The length is one less than the number of pixel pairs because of the - // way the jmp works in the PIO program (it's a do-while loop). - pixels: [0; NUM_PIXEL_PAIRS_PER_LINE + 1], -}; - // ----------------------------------------------------------------------------- // Functions // ----------------------------------------------------------------------------- @@ -233,7 +96,7 @@ fn main() -> ! { // Grab the singleton containing all the RP2040 peripherals let mut pac = pac::Peripherals::take().unwrap(); // Grab the singleton containing all the generic Cortex-M peripherals - let core = pac::CorePeripherals::take().unwrap(); + let mut core = pac::CorePeripherals::take().unwrap(); // Reset the DMA engine. If we don't do this, starting from probe-run // (as opposed to a cold-start) is unreliable. @@ -288,10 +151,6 @@ fn main() -> ! { info!("Clocks OK"); - // Create an object we can use to busy-wait for specified numbers of - // milliseconds. For this to work, it needs to know our clock speed. - let _delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().integer()); - // sio is the *Single-cycle Input/Output* peripheral. It has all our GPIO // pins, as well as some mailboxes and other useful things for inter-core // communications. @@ -327,411 +186,28 @@ fn main() -> ! { let _blue2 = pins.gpio12.into_mode::(); let _blue3 = pins.gpio13.into_mode::(); - // Set up a checker board in the line buffers - unsafe { - PIXEL_DATA_BUFFER_EVEN.pixels[0] = NUM_PIXEL_PAIRS_PER_LINE as u32 - 1; - for px in PIXEL_DATA_BUFFER_EVEN.pixels[1..].iter_mut() { - *px = 0x0FFF_0000; - } - PIXEL_DATA_BUFFER_ODD.pixels[0] = NUM_PIXEL_PAIRS_PER_LINE as u32 - 1; - for px in PIXEL_DATA_BUFFER_ODD.pixels[1..].iter_mut() { - *px = 0x0000_0FFF; - } - } - - // Grab PIO0 and the state machines it contains - let (mut pio, sm0, sm1, _sm2, _sm3) = pac.PIO0.split(&mut pac.RESETS); - - // This program runs the timing loop. We post timing data (i.e. the length - // of each period, along with what the H-Sync and V-Sync pins should do) - // and it sets the GPIO pins and busy-waits the appropriate amount of - // time. It also takes an extra 'instruction' which we can use to trigger - // the appropriate interrupts. - // - // Post where value: - // - // - // The SM will execute the instruction (typically either a NOP or an IRQ), - // set the H-Sync and V-Sync pins as desired, then wait the given number - // of clock cycles. - // - // Note: autopull should be set to 32-bits, OSR is set to shift right. - let timing_program = pio_proc::pio!( - 32, - " - .wrap_target - ; Step 1. Push next 2 bits of OSR into `pins`, to set H-Sync and V-Sync - out pins, 2 - ; Step 2. Push last 14 bits of OSR into X for the timing loop. - out x, 14 - ; Step 3. Execute bottom 16-bits of OSR as an instruction. This take two cycles. - out exec, 16 - loop0: - ; Spin until X is zero - jmp x-- loop0 - .wrap - " - ); - - // This is the video pixels program. It waits for an IRQ - // (posted by the timing loop) then pulls pixel data from the FIFO. We post - // the number of pixels for that line, then the pixel data. - // - // Post ... ; each maps to - // the RGB output pins. On a Neotron Pico, there are 12 (4 Red, 4 Green and - // 4 Blue) - so we set autopull to 12, and each value should be 12-bits long. - // - // Currently the FIFO supplies only the pixels, not the length value. When - // we read the length from the FIFO as well, all hell breaks loose. - // - // Note autopull should be set to 32-bits, OSR is set to shift right. - let pixel_program = pio_proc::pio!( - 32, - " - .wrap_target - ; Wait for timing state machine to start visible line - wait 1 irq 0 - ; Read the line length (in pixel-pairs) - out x, 32 - loop1: - ; Write out first pixel - takes 5 clocks per pixel (double-width) - out pins, 16 [4] - ; Write out second pixel - takes 5 clocks per pixel (double-width, allowing one for the jump) - out pins, 16 [3] - ; Repeat until all pixel pairs sent - jmp x-- loop1 - ; Clear all pins after visible section - mov pins null - .wrap - " - ); - - // These two state machines run thus: - // - // | Clock | Timing PIOSM | Pixel PIOSM | - // |:------|:-------------|:-----------------| - // | 1 | out pins, 2 | wait 1 irq 0 | - // | 2 | out x, 14 | wait 1 irq 0 | - // | 3 | out exec, 16 | wait 1 irq 0 | - // | 4 | | wait 1 irq 0 | - // | 5 | jmp x-- | wait 1 irq 0 | - // | 6 | | set x, 31 | - // | 7 | | out pins, 16 [4] | - // | 8 | | .. | - // | 9 | | .. | - // | 10 | | .. | - // | 11 | | .. | - // | 12 | | out pins, 16 [3] | - // | 13 | | .. | - // | 14 | | .. | - // | 15 | | .. | - // | 16 | | jump x-- loop1 | - // - // Note: Credit to - // https://gregchadwick.co.uk/blog/playing-with-the-pico-pt5/ who had a - // very similar idea to me, but wrote it up far better than I ever could. - - let timing_installed = pio.install(&timing_program.program).unwrap(); - let (mut timing_sm, _, timing_fifo) = hal_pio::PIOBuilder::from_program(timing_installed) - .buffers(hal_pio::Buffers::OnlyTx) - .out_pins(0, 2) - .autopull(true) - .out_shift_direction(hal_pio::ShiftDirection::Right) - .pull_threshold(32) - .build(sm0); - timing_sm.set_pindirs([(0, hal_pio::PinDir::Output), (1, hal_pio::PinDir::Output)]); - - // Important notes! - // - // You must not set a clock_divider (other than 1.0) on the pixel state - // machine. You might want the pixels to be twice as wide (or mode), but - // enabling a clock divider adds a lot of jitter (i.e. the start each - // each line differs by some number of 126 MHz clock cycles). - - let pixels_installed = pio.install(&pixel_program.program).unwrap(); - let (mut pixel_sm, _, pixel_fifo) = hal_pio::PIOBuilder::from_program(pixels_installed) - .buffers(hal_pio::Buffers::OnlyTx) - .out_pins(2, 12) - .autopull(true) - .out_shift_direction(hal_pio::ShiftDirection::Right) - .pull_threshold(32) - .build(sm1); - pixel_sm.set_pindirs((2..=13).map(|x| (x, hal_pio::PinDir::Output))); - - unsafe { - // Read from the timing buffer and write to the timing FIFO. We get an - // IRQ when the transfer is complete (i.e. when line has been fully - // loaded). - pac.DMA.ch[TIMING_DMA_CHAN].ch_ctrl_trig.write(|w| { - w.data_size().size_word(); - w.incr_read().set_bit(); - w.incr_write().clear_bit(); - w.treq_sel().bits(timing_fifo.dreq_value()); - w.chain_to().bits(TIMING_DMA_CHAN as u8); - w.ring_size().bits(0); - w.ring_sel().clear_bit(); - w.bswap().clear_bit(); - w.irq_quiet().clear_bit(); - w.en().set_bit(); - w.sniff_en().clear_bit(); - w - }); - pac.DMA.ch[TIMING_DMA_CHAN] - .ch_read_addr - .write(|w| w.bits(TIMING_BUFFER.visible_line.data.as_ptr() as usize as u32)); - pac.DMA.ch[TIMING_DMA_CHAN] - .ch_write_addr - .write(|w| w.bits(timing_fifo.fifo_address() as usize as u32)); - pac.DMA.ch[TIMING_DMA_CHAN] - .ch_trans_count - .write(|w| w.bits(4)); - - // Read from the pixel buffer (even first) and write to the pixel FIFO - pac.DMA.ch[PIXEL_DMA_CHAN].ch_ctrl_trig.write(|w| { - w.data_size().size_word(); - w.incr_read().set_bit(); - w.incr_write().clear_bit(); - w.treq_sel().bits(pixel_fifo.dreq_value()); - w.chain_to().bits(PIXEL_DMA_CHAN as u8); - w.ring_size().bits(0); - w.ring_sel().clear_bit(); - w.bswap().clear_bit(); - w.irq_quiet().clear_bit(); - w.en().set_bit(); - w.sniff_en().clear_bit(); - w - }); - pac.DMA.ch[PIXEL_DMA_CHAN] - .ch_read_addr - .write(|w| w.bits(PIXEL_DATA_BUFFER_EVEN.as_ptr())); - pac.DMA.ch[PIXEL_DMA_CHAN] - .ch_write_addr - .write(|w| w.bits(pixel_fifo.fifo_address() as usize as u32)); - pac.DMA.ch[PIXEL_DMA_CHAN] - .ch_trans_count - .write(|w| w.bits(NUM_PIXEL_PAIRS_PER_LINE as u32 + 1)); - pac.DMA.inte0.write(|w| { - w.inte0() - .bits((1 << PIXEL_DMA_CHAN) | (1 << TIMING_DMA_CHAN)) - }); - - // Hand off the DMA peripheral to the interrupt - DMA_PERIPH = Some(pac.DMA); - - cortex_m::interrupt::enable(); - - // Enable the interupts (DMA_PERIPH has to be set first) - pac::NVIC::unpend(pac::Interrupt::DMA_IRQ_0); - pac::NVIC::unmask(pac::Interrupt::DMA_IRQ_0); - - info!("IRQs enabled"); - - // Enable the DMA - DMA_PERIPH - .as_mut() - .unwrap() - .multi_chan_trigger - .write(|w| w.bits((1 << PIXEL_DMA_CHAN) | (1 << TIMING_DMA_CHAN))); - - info!("DMA enabled"); - } + vga::init(pac.PIO0, pac.DMA, &mut pac.RESETS); - info!("DMA set-up complete"); + core.SYST + .set_clock_source(cortex_m::peripheral::syst::SystClkSource::External); + core.SYST.set_reload(0x00FF_FFFF); + core.SYST.clear_current(); + core.SYST.enable_counter(); - timing_sm.start(); - pixel_sm.start(); + let mut video = vga::VideoEngine::new(); - info!("State Machines running"); - - let mut frame_count = 0; loop { cortex_m::asm::wfi(); - if DMA_READY.load(Ordering::Relaxed) { - DMA_READY.store(false, Ordering::Relaxed); - let current_line = CURRENT_DISPLAY_LINE.load(Ordering::Relaxed); - if current_line == 0 { - info!("Frame {}", frame_count); - frame_count += 1; - } - // new line - do some painting! - let px_buf = unsafe { - if (current_line & 1) == 0 { - &mut PIXEL_DATA_BUFFER_ODD - } else { - &mut PIXEL_DATA_BUFFER_EVEN - } - }; - - let text_row = (current_line / 16) as usize; - let font_row = (current_line % 16) as usize; - let text_row_offset = text_row * 80; - - if text_row < 25 { - let row_slice = unsafe { &CHAR_ARRAY[text_row_offset..text_row_offset + 80] }; - let font_ptr = unsafe { freebsd_cp850::FONT_DATA.as_ptr().add(font_row) }; - - let px_ptr = px_buf.pixels[1..].as_mut_ptr(); - let mut px_idx = 0; - for ch in row_slice.iter() { - let index = (*ch as isize) * 16; - let mono_pixels = unsafe { *font_ptr.offset(index) } as usize; - // Convert from mono to RGB white - unsafe { - core::ptr::write_volatile( - px_ptr.offset(px_idx + 0), - LOOKUP[(mono_pixels >> 6) & 3], - ); - core::ptr::write_volatile( - px_ptr.offset(px_idx + 1), - LOOKUP[(mono_pixels >> 4) & 3], - ); - core::ptr::write_volatile( - px_ptr.offset(px_idx + 2), - LOOKUP[(mono_pixels >> 2) & 3], - ); - core::ptr::write_volatile( - px_ptr.offset(px_idx + 3), - LOOKUP[(mono_pixels >> 0) & 3], - ); - } - px_idx += 4; - } - } - } - } -} - -static LOOKUP: [u32; 4] = [0x0F00_0F00, 0x00FF_0F00, 0x0F00_00FF, 0x00FF_00FF]; - -const fn make_timing(period: u32, hsync: bool, vsync: bool, raise_irq: bool) -> u32 { - let command = if raise_irq { - // This command sets IRQ 0. It is the same as: - // - // ``` - // pio::InstructionOperands::IRQ { - // clear: false, - // wait: false, - // index: 0, - // relative: false, - // }.encode() - // ``` - // - // Unfortunately encoding this isn't a const-fn, so we cheat: - 0xc000 - } else { - // This command is a no-op (it moves Y into Y) - // - // ``` - // pio::InstructionOperands::MOV { - // destination: pio::MovDestination::Y, - // op: pio::MovOperation::None, - // source: pio::MovSource::Y, - // }.encode() - // ``` - // - // Unfortunately encoding this isn't a const-fn, so we cheat: - 0xa042 - }; - let mut value: u32 = 0; - if hsync { - value |= 1 << 0; - } - if vsync { - value |= 1 << 1; + // This function currently consumes about 70% CPU (or rather, 90% CPU + // on each of 400 lines, and 0% CPU on the other 50 lines) + video.poll(); } - value |= (period - 6) << 2; - value | command << 16 } #[interrupt] -unsafe fn DMA_IRQ_0() { - let dma: &mut pac::DMA = match DMA_PERIPH.as_mut() { - Some(dma) => dma, - None => { - return; - } - }; - let status = dma.ints0.read().bits(); - - // Check if this is a DMA interrupt for the sync DMA channel - let timing_dma_chan_irq = (status & (1 << TIMING_DMA_CHAN)) != 0; - - // Check if this is a DMA interrupt for the line DMA channel - let pixel_dma_chan_irq = (status & (1 << PIXEL_DMA_CHAN)) != 0; - - if timing_dma_chan_irq { - // clear timing_dma_chan bit in DMA interrupt bitfield - dma.ints0.write(|w| w.bits(1 << TIMING_DMA_CHAN)); - - let old_timing_line = CURRENT_TIMING_LINE.load(Ordering::Relaxed); - let timing_line = if old_timing_line == 449 { - // 449 -> 0 - 0 - } else { - // n -> n + 1 - old_timing_line + 1 - }; - CURRENT_TIMING_LINE.store(timing_line, Ordering::Relaxed); - - let buffer = match timing_line { - 0..=LAST_LINE => { - // Visible lines - &TIMING_BUFFER.visible_line - } - NUM_LINES..=411 => { - // VGA front porch before VGA sync pulse - &TIMING_BUFFER.vblank_porch_buffer - } - 412..=415 => { - // Sync pulse - &TIMING_BUFFER.vblank_sync_buffer - } - 416.. => { - // VGA back porch following VGA sync pulse - &TIMING_BUFFER.vblank_porch_buffer - } - }; - dma.ch[TIMING_DMA_CHAN] - .ch_al3_read_addr_trig - .write(|w| w.bits(buffer as *const _ as usize as u32)) - } - - if pixel_dma_chan_irq { - dma.ints0.write(|w| w.bits(1 << PIXEL_DMA_CHAN)); - - // A pixel DMA transfer is now complete. This only fires on lines 0..=480. - - let mut next_display_line = CURRENT_DISPLAY_LINE.load(Ordering::Relaxed) + 1; - if next_display_line > LAST_LINE { - next_display_line = 0; - }; - - // Set the DMA load address according to which line we are on. We use - // the 'trigger' alias to restart the DMA at the same time as we - // write the new read address. The DMA had stopped because the - // previous line was transferred completely. - if (next_display_line & 1) == 1 { - // Odd visible line is next - dma.ch[PIXEL_DMA_CHAN] - .ch_al3_read_addr_trig - .write(|w| w.bits(PIXEL_DATA_BUFFER_ODD.as_ptr())) - } else { - // Even visible line is next - dma.ch[PIXEL_DMA_CHAN] - .ch_al3_read_addr_trig - .write(|w| w.bits(PIXEL_DATA_BUFFER_EVEN.as_ptr())) - } - - CURRENT_DISPLAY_LINE.store(next_display_line, Ordering::Relaxed); - DMA_READY.store(true, Ordering::Relaxed); - } -} - -impl LineBuffer { - /// Convert the line buffer to a 32-bit address that the DMA engine understands. - fn as_ptr(&self) -> u32 { - // NB: skip the length field - self.pixels.as_ptr() as usize as u32 +fn DMA_IRQ_0() { + unsafe { + vga::irq(); } } diff --git a/src/freebsd_cp850.rs b/src/vga/font.rs similarity index 97% rename from src/freebsd_cp850.rs rename to src/vga/font.rs index e167918..e03536e 100644 --- a/src/freebsd_cp850.rs +++ b/src/vga/font.rs @@ -1,33 +1,42 @@ -/// The font data here must be in the same order as the `Char` enum. This is -/// the cp850-8x16 font from FreeBSD. See -/// http://web.mit.edu/freebsd/head/share/syscons/fonts/cp850-8x16.fnt -/// -/// The compilation of software known as FreeBSD is distributed under the -/// following terms: -/// -/// Copyright (c) 1992-2014 The FreeBSD Project. All rights reserved. -/// -/// Redistribution and use in source and binary forms, with or without -/// modification, are permitted provided that the following conditions -/// are met: -/// 1. Redistributions of source code must retain the above copyright -/// notice, this list of conditions and the following disclaimer. -/// 2. Redistributions in binary form must reproduce the above copyright -/// notice, this list of conditions and the following disclaimer in the -/// documentation and/or other materials provided with the distribution. -/// -/// THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND -/// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -/// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -/// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE -/// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -/// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS -/// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -/// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -/// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY -/// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF -/// SUCH DAMAGE. -pub static FONT_DATA: [u8; 256 * 16] = [ +//! # Neotron Pico Console Font +//! +//! This is the cp850-8x16 font from FreeBSD. See +//! +//! +//! The compilation of software known as FreeBSD is distributed under the +//! following terms: +//! +//! Copyright (c) 1992-2014 The FreeBSD Project. All rights reserved. +//! +//! Redistribution and use in source and binary forms, with or without +//! modification, are permitted provided that the following conditions +//! are met: +//! 1. Redistributions of source code must retain the above copyright +//! notice, this list of conditions and the following disclaimer. +//! 2. Redistributions in binary form must reproduce the above copyright +//! notice, this list of conditions and the following disclaimer in the +//! documentation and/or other materials provided with the distribution. +//! +//! THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +//! ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +//! IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +//! ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +//! FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +//! DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +//! OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +//! HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +//! LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +//! OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +//! SUCH DAMAGE. + +/// Width of the font in pixels +pub const WIDTH_PX: usize = 8; + +/// Height of the font in pixels +pub const HEIGHT_PX: usize = 16; + +/// Our font data - arranged as 256 glyphs of 1 byte/row x 16 row/glyph. +pub static DATA: [u8; 256 * HEIGHT_PX] = [ // Char::Null 0b0000_0000, 0b0000_0000, diff --git a/src/vga/mod.rs b/src/vga/mod.rs new file mode 100644 index 0000000..6c0f9ff --- /dev/null +++ b/src/vga/mod.rs @@ -0,0 +1,770 @@ +//! # VGA Driver for the Neotron Pico +//! +//! VGA output on the Neotron Pico uses 14 GPIO pins and two PIO state machines. +//! +//! It can generate 640x480@60Hz and 640x400@70Hz standard VGA video, with a +//! 25.2 MHz pixel clock. The spec is 25.175 MHz, so we are 0.1% off). The +//! assumption is that the CPU is clocked at 126 MHz, i.e. 5x the pixel +//! clock. All of the PIO code relies on this assumption! +//! +//! Currently only an 80x25 two-colour text-mode is supported. Other modes will be +//! added in the future. + +// ----------------------------------------------------------------------------- +// Licence Statement +// ----------------------------------------------------------------------------- +// Copyright (c) Jonathan 'theJPster' Pallant and the Neotron Developers, 2021 +// +// This program is free software: you can redistribute it and/or modify it under +// the terms of the GNU General Public License as published by the Free Software +// Foundation, either version 3 of the License, or (at your option) any later +// version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +// details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . +// ----------------------------------------------------------------------------- + +// ----------------------------------------------------------------------------- +// Sub-modules +// ----------------------------------------------------------------------------- + +pub(crate) mod font; + +// ----------------------------------------------------------------------------- +// Imports +// ----------------------------------------------------------------------------- + +use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; +use defmt::*; +use pico::hal::pio::PIOExt; + +// ----------------------------------------------------------------------------- +// Types +// ----------------------------------------------------------------------------- + +/// Holds some data necessary to run the Video. +/// +/// This structure is owned entirely by the main thread (or the drawing +/// thread). Data handled under interrupt is stored in various other places. +pub struct VideoEngine { + /// How many frames have been drawn + frame_count: u32, + // How many ticks the CPU spent drawing, across the entire frame. + running_ticks: u32, + /// Look-up table mapping two 1-bpp pixels to two 12-bit RGB values (packed into one 32-bit word). + /// + /// You can adjust this table to convert text to different colours. + lookup: [RGBPair; 4], +} + +#[repr(C, align(16))] +struct LineBuffer { + length: u32, + pixels: [RGBPair; NUM_PIXEL_PAIRS_PER_LINE], +} +/// Holds the four scan-line timing FIFO words we need for one scan-line. +/// +/// See `make_timing` for a function which can generate these words. We DMA +/// them into the timing FIFO, so they must sit on a 16-byte boundary. +#[repr(C, align(16))] +struct ScanlineTimingBuffer { + data: [u32; 4], +} + +/// Holds the different kinds of scan-line timing buffers we need for various +/// portions of the screen. +#[repr(C, align(16))] +struct TimingBuffer { + /// We use this when there are visible pixels on screen + visible_line: ScanlineTimingBuffer, + /// We use this during the v-sync front-porch and v-sync back-porch + vblank_porch_buffer: ScanlineTimingBuffer, + /// We use this during the v-sync sync pulse + vblank_sync_buffer: ScanlineTimingBuffer, +} + +/// Represents a 12-bit colour value. +/// +/// Each channel has four-bits, and they are packed in `GBR` format. This is +/// so the PIO can shift them out right-first, and we have RED0 assigned to +/// the lowest GPIO pin. +#[repr(transparent)] +#[derive(Copy, Clone, PartialEq, Eq)] +pub struct RGBColour(u16); + +impl RGBColour { + pub const fn from_24bit(red: u8, green: u8, blue: u8) -> RGBColour { + let red: u16 = (red as u16) & 0x00F; + let green: u16 = (green as u16) & 0x00F; + let blue: u16 = (blue as u16) & 0x00F; + RGBColour((blue << 12) | (green << 4) | red) + } +} + +/// Represents two `RGBColour` pixels packed together. +/// +/// The `first` pixel is packed in the lower 16-bits. This is because the PIO +/// shifts-right. +#[repr(transparent)] +#[derive(Copy, Clone, PartialEq, Eq)] +pub struct RGBPair(u32); + +impl RGBPair { + pub const fn from_pixels(first: RGBColour, second: RGBColour) -> RGBPair { + let first: u32 = first.0 as u32; + let second: u32 = second.0 as u32; + RGBPair((second << 16) | first) + } +} + +// ----------------------------------------------------------------------------- +// Static and Const Data +// ----------------------------------------------------------------------------- + +/// How many pixels per scan-line. +/// +/// Adjust the pixel PIO program to run at the right speed to the screen is +/// filled. For example, if this is only 320 but you are aiming at 640x480, +/// make the pixel PIO take twice as long per pixel. +const NUM_PIXELS_PER_LINE: usize = 640; + +/// How many pixel pairs we send out. +/// +/// Each pixel is two 12-bit values packed into one 32-bit word(an `RGBPair`). +/// This is to make more efficient use of DMA and FIFO resources. +const NUM_PIXEL_PAIRS_PER_LINE: usize = NUM_PIXELS_PER_LINE / 2; + +/// Number of lines on screen. +const NUM_LINES: usize = 400; + +/// The highest number of columns in any text mode. +pub const NUM_TEXT_COLS: usize = NUM_PIXELS_PER_LINE / font::WIDTH_PX; + +/// The highest number of rows in any text mode. +pub const NUM_TEXT_ROWS: usize = NUM_LINES as usize / font::HEIGHT_PX; + +/// Scan-line on which the visible portion of the screen ends. +/// +/// This value is for 400 line, 70 Hz mode. We assume the visible portion starts on line zero. +const V_VISIBLE_END: usize = V_FRONT_PORCH_START - 1; + +/// Scan-line on which the front-porch starts. +/// +/// This value is for 400 line, 70 Hz mode. +const V_FRONT_PORCH_START: usize = NUM_LINES; + +/// Scan-line on which the front-porch ends. +/// +/// This value is for 400 line, 70 Hz mode. +const V_FRONT_PORCH_END: usize = V_SYNC_START - 1; + +/// Scan-line on which the sync-pulse starts. +/// +/// This value is for 400 line, 70 Hz mode. +const V_SYNC_START: usize = 412; + +/// Scan-line on which the sync pulse ends. +/// +/// This value is for 400 line, 70 Hz mode. +const V_SYNC_END: usize = V_BACK_PORCH_START - 1; + +/// Scan-line on which the back-porch starts. +/// +/// This value is for 400 line, 70 Hz mode. +const V_BACK_PORCH_START: usize = 416; + +/// Scan-line on which the back-porch ends. +/// +/// This value is for 400 line, 70 Hz mode. +const V_BACK_PORCH_END: usize = 449; + +/// Stores our timing data which we DMA into the timing PIO State Machine +static TIMING_BUFFER: TimingBuffer = TimingBuffer { + // Note - the order of the arguments to `make_timing` is: + // + // * H-Sync Pulse (true = high, false = low) + // * V-Sync Pulse (true = high, false = low) + // * Generate pixel start IRQ (true or false) + visible_line: ScanlineTimingBuffer { + data: [ + // Front porch (as per the spec) + make_timing(16 * 5, true, false, false), + // Sync pulse (as per the spec) + make_timing(96 * 5, false, false, false), + // Back porch (shortened by two pixels because the + // video starts two pixels late) + make_timing(48 * 5, true, false, false), + // Visible portion. It also triggers the IRQ to start pixels moving. + make_timing(640 * 5, true, false, true), + ], + }, + vblank_porch_buffer: ScanlineTimingBuffer { + data: [ + // Front porch + make_timing(16 * 5, true, false, false), + // Sync pulse + make_timing(96 * 5, false, false, false), + // Back porch + make_timing(48 * 5, true, false, false), + // 'visible' portion (but it's blank) + make_timing(640 * 5, true, false, false), + ], + }, + vblank_sync_buffer: ScanlineTimingBuffer { + data: [ + // Front porch + make_timing(16 * 5, true, true, false), + // Sync pulse + make_timing(96 * 5, false, true, false), + // Back porch + make_timing(48 * 5, true, true, false), + // Visible portion (but it's blank) + make_timing(640 * 5, true, true, false), + ], + }, +}; + +/// Tracks which scan-line we are currently on (for timing purposes => it goes 0..=449) +static CURRENT_TIMING_LINE: AtomicUsize = AtomicUsize::new(0); + +/// Tracks which scan-line we are currently on (for pixel purposes => it goes 0..NUM_LINES) +static CURRENT_DISPLAY_LINE: AtomicUsize = AtomicUsize::new(0); + +/// Set to `true` when DMA of previous line is complete and next line is scheduled. +static DMA_READY: AtomicBool = AtomicBool::new(false); + +/// Somewhere to stash the DMA controller object, so the IRQ can find it +static mut DMA_PERIPH: Option = None; + +/// DMA channel for the timing FIFO +const TIMING_DMA_CHAN: usize = 0; + +/// DMA channel for the pixel FIFO +const PIXEL_DMA_CHAN: usize = 1; + +/// 12-bit pixels for the even scan-lines (0, 2, 4 ... NUM_LINES - 2). Defaults to black. +static mut PIXEL_DATA_BUFFER_EVEN: LineBuffer = LineBuffer { + length: (NUM_PIXEL_PAIRS_PER_LINE as u32) - 1, + pixels: [RGBPair::from_pixels(colours::WHITE, colours::BLACK); NUM_PIXEL_PAIRS_PER_LINE], +}; + +/// 12-bit pixels for the odd scan-lines (1, 3, 5 ... NUM_LINES-1). Defaults to white. +static mut PIXEL_DATA_BUFFER_ODD: LineBuffer = LineBuffer { + length: (NUM_PIXEL_PAIRS_PER_LINE as u32) - 1, + pixels: [RGBPair::from_pixels(colours::BLACK, colours::WHITE); NUM_PIXEL_PAIRS_PER_LINE], +}; + +/// Some dummy text to display. +/// +/// This is arranged as `NUM_TEXT_ROWS` rows of `NUM_TEXT_COLS` columns. Each +/// item is an index into `font::FONT_DATA` (or a Code-Page 850 character). +static mut CHAR_ARRAY: [u8; NUM_TEXT_COLS as usize * NUM_TEXT_ROWS as usize] = +*b"\ +\xDA\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xBF\ +\xB3Neotron Pico Booting... \xB3\ +\xB3 \xB3\ +\xB3 \xB3\ +\xB3 \xB3\ +\xB3 \xB3\ +\xB3 \xDb\xDb\xDb\xBB\xB2\xB2\xDb\xDb\xBB\xDb\xDb\xDb\xDb\xDb\xDb\xDb\xBB\xB2\xDb\xDb\xDb\xDb\xDb\xBB\xB2\xDb\xDb\xDb\xDb\xDb\xDb\xDb\xDb\xBB\xDb\xDb\xDb\xDb\xDb\xDb\xBB\xB2\xB2\xDb\xDb\xDb\xDb\xDb\xBB\xB2\xDb\xDb\xDb\xBB\xB2\xB2\xDb\xDb\xBB \xB3\ +\xB3 \xDb\xDb\xDb\xDb\xBB\xB2\xDb\xDb\xBA\xDb\xDb\xC9\xCD\xCD\xCD\xCD\xBC\xDb\xDb\xC9\xCD\xCD\xDb\xDb\xBB\xC8\xCD\xCD\xDb\xDb\xC9\xCD\xCD\xBC\xDb\xDb\xC9\xCD\xCD\xDb\xDb\xBB\xDb\xDb\xC9\xCD\xCD\xDb\xDb\xBB\xDb\xDb\xDb\xDb\xBB\xB2\xDb\xDb\xBA \xB3\ +\xB3 \xDb\xDb\xC9\xDb\xDb\xBB\xDb\xDb\xBA\xDb\xDb\xDb\xDb\xDb\xBB\xB2\xB2\xDb\xDb\xBA\xB2\xB2\xDb\xDb\xBA\xB2\xB2\xB2\xDb\xDb\xBA\xB2\xB2\xB2\xDb\xDb\xDb\xDb\xDb\xDb\xC9\xBC\xDb\xDb\xBA\xB2\xB2\xDb\xDb\xBA\xDb\xDb\xC9\xDb\xDb\xBB\xDb\xDb\xBA \xB3\ +\xB3 \xDb\xDb\xBA\xC8\xDb\xDb\xDb\xDb\xBA\xDb\xDb\xC9\xCD\xCD\xBC\xB2\xB2\xDb\xDb\xBA\xB2\xB2\xDb\xDb\xBA\xB2\xB2\xB2\xDb\xDb\xBA\xB2\xB2\xB2\xDb\xDb\xC9\xCD\xCD\xDb\xDb\xBB\xDb\xDb\xBA\xB2\xB2\xDb\xDb\xBA\xDb\xDb\xBA\xC8\xDb\xDb\xDb\xDb\xBA \xB3\ +\xB3 \xDb\xDb\xBA\xB2\xC8\xDb\xDb\xDb\xBA\xDb\xDb\xDb\xDb\xDb\xDb\xDb\xBB\xC8\xDb\xDb\xDb\xDb\xDb\xC9\xBC\xB2\xB2\xB2\xDb\xDb\xBA\xB2\xB2\xB2\xDb\xDb\xBA\xB2\xB2\xDb\xDb\xBA\xC8\xDb\xDb\xDb\xDb\xDb\xC9\xBC\xDb\xDb\xBA\xB2\xC8\xDb\xDb\xDb\xBA \xB3\ +\xB3 \xB3\ +\xB3 \xB3\ +\xB3 \xB3\ +\xB3 \xB3\ +\xB3 \xB3\ +\xB3 \xB3\ +\xB3 \xB3\ +\xB3 \xB3\ +\xB3 \xB3\ +\xB3 \xB3\ +\xB3 \xB3\ +\xB3 \xB3\ +\xB3This is the end \xB3\ +\xC0\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xD9"; + +/// A set of useful constants representing common RGB colours. +pub mod colours { + /// The colour white + pub const WHITE: super::RGBColour = super::RGBColour(0xFFF); + + /// The colour black + pub const BLACK: super::RGBColour = super::RGBColour(0x000); + + /// The colour blue + pub const BLUE: super::RGBColour = super::RGBColour(0xF00); + + /// The colour green + pub const GREEN: super::RGBColour = super::RGBColour(0x0F0); + + /// The colour red + pub const RED: super::RGBColour = super::RGBColour(0x00F); +} + +// ----------------------------------------------------------------------------- +// Functions +// ----------------------------------------------------------------------------- + +impl VideoEngine { + // Initialise the main-thread resources + pub fn new() -> VideoEngine { + VideoEngine { + frame_count: 0, + running_ticks: 0, + lookup: [ + RGBPair::from_pixels(colours::BLUE, colours::BLUE), + RGBPair::from_pixels(colours::BLUE, colours::WHITE), + RGBPair::from_pixels(colours::WHITE, colours::BLUE), + RGBPair::from_pixels(colours::WHITE, colours::WHITE), + ], + } + } + + pub fn poll(&mut self) { + if DMA_READY.load(Ordering::Relaxed) { + let awake_at = cortex_m::peripheral::SYST::get_current(); + DMA_READY.store(false, Ordering::Relaxed); + let current_line_num = CURRENT_DISPLAY_LINE.load(Ordering::Relaxed); + if current_line_num == 0 { + let cpu_percent = ((self.running_ticks * 1000 * 100) / 400) / 31_777; + info!( + "Frame {}, awake {} ticks ({}% load)", + self.frame_count, self.running_ticks, cpu_percent + ); + self.running_ticks = 0; + self.frame_count += 1; + } + + // new line - pick a buffer to draw into (not the one that is currently rendering!) + let scan_line_buffer = unsafe { + if (current_line_num & 1) == 0 { + &mut PIXEL_DATA_BUFFER_ODD + } else { + &mut PIXEL_DATA_BUFFER_EVEN + } + }; + + // Convert our position in scan-lines to a text row, and a line within each glyph on that row + let text_row = (current_line_num / font::HEIGHT_PX) as usize; + let font_row = (current_line_num % font::HEIGHT_PX) as usize; + + if text_row < NUM_TEXT_ROWS { + // Note (unsafe): We could stash the char array inside `self` + // but at some point we are going to need one CPU rendering + // the text, and the other CPU running code and writing to + // the buffer. This might be Undefined Behaviour, but + // unfortunately real-time video is all about shared mutable + // state. At least our platform is fixed, so we can simply + // test if it works, for some given version of the Rust compiler. + let row_slice = unsafe { + &CHAR_ARRAY[(text_row * NUM_TEXT_COLS)..((text_row + 1) * NUM_TEXT_COLS)] + }; + // Every font look-up we are about to do for this row will + // involve offsetting by the row within each glyph. As this + // is the same for every glyph on this row, we calculate a + // new pointer once, in advance, and save ourselves an + // addition each time around the loop. + let font_ptr = unsafe { font::DATA.as_ptr().add(font_row) }; + + // Get a pointer into our scan-line buffer + let scan_line_buffer_ptr = scan_line_buffer.pixels.as_mut_ptr(); + let mut px_idx = 0; + + // Convert from characters to coloured pixels, using the font as a look-up table. + for ch in row_slice.iter() { + let index = (*ch as isize) * 16; + // Note (unsafe): We use pointer arithmetic here because we + // can't afford a bounds-check on an array. This is safe + // because the font is `256 * width` bytes long and we can't + // index more than `255 * width` bytes into it. + let mono_pixels = unsafe { *font_ptr.offset(index) } as usize; + // Convert from eight mono pixels in one byte to four RGB pairs + unsafe { + core::ptr::write_volatile( + scan_line_buffer_ptr.offset(px_idx + 0), + self.lookup[(mono_pixels >> 6) & 3], + ); + core::ptr::write_volatile( + scan_line_buffer_ptr.offset(px_idx + 1), + self.lookup[(mono_pixels >> 4) & 3], + ); + core::ptr::write_volatile( + scan_line_buffer_ptr.offset(px_idx + 2), + self.lookup[(mono_pixels >> 2) & 3], + ); + core::ptr::write_volatile( + scan_line_buffer_ptr.offset(px_idx + 3), + self.lookup[(mono_pixels >> 0) & 3], + ); + } + px_idx += 4; + } + } + let running_end = cortex_m::peripheral::SYST::get_current(); + self.running_ticks += (awake_at - running_end) & 0x00FF_FFFF; + } + } +} + +/// Initialise all the static data and peripherals we need for our video display. +/// +/// We need to keep `pio` and `dma` to run the video. We need `resets` to set +/// things up, so we only borrow that. +pub fn init(pio: super::pac::PIO0, dma: super::pac::DMA, resets: &mut super::pac::RESETS) { + // Grab PIO0 and the state machines it contains + let (mut pio, sm0, sm1, _sm2, _sm3) = pio.split(resets); + + // This program runs the timing loop. We post timing data (i.e. the length + // of each period, along with what the H-Sync and V-Sync pins should do) + // and it sets the GPIO pins and busy-waits the appropriate amount of + // time. It also takes an extra 'instruction' which we can use to trigger + // the appropriate interrupts. + // + // Post where value: + // + // + // The SM will execute the instruction (typically either a NOP or an IRQ), + // set the H-Sync and V-Sync pins as desired, then wait the given number + // of clock cycles. + // + // Note: autopull should be set to 32-bits, OSR is set to shift right. + let timing_program = pio_proc::pio!( + 32, + " + .wrap_target + ; Step 1. Push next 2 bits of OSR into `pins`, to set H-Sync and V-Sync + out pins, 2 + ; Step 2. Push last 14 bits of OSR into X for the timing loop. + out x, 14 + ; Step 3. Execute bottom 16-bits of OSR as an instruction. This take two cycles. + out exec, 16 + loop0: + ; Spin until X is zero + jmp x-- loop0 + .wrap + " + ); + + // This is the video pixels program. It waits for an IRQ + // (posted by the timing loop) then pulls pixel data from the FIFO. We post + // the number of pixels for that line, then the pixel data. + // + // Post ... ; each maps to + // the RGB output pins. On a Neotron Pico, there are 12 (4 Red, 4 Green and + // 4 Blue) - so we set autopull to 12, and each value should be 12-bits long. + // + // Currently the FIFO supplies only the pixels, not the length value. When + // we read the length from the FIFO as well, all hell breaks loose. + // + // Note autopull should be set to 32-bits, OSR is set to shift right. + let pixel_program = pio_proc::pio!( + 32, + " + .wrap_target + ; Wait for timing state machine to start visible line + wait 1 irq 0 + ; Read the line length (in pixel-pairs) + out x, 32 + loop1: + ; Write out first pixel - takes 5 clocks per pixel (double-width) + out pins, 16 [4] + ; Write out second pixel - takes 5 clocks per pixel (double-width, allowing one for the jump) + out pins, 16 [3] + ; Repeat until all pixel pairs sent + jmp x-- loop1 + ; Clear all pins after visible section + mov pins null + .wrap + " + ); + + // These two state machines run thus: + // + // | Clock | Timing PIOSM | Pixel PIOSM | + // |:------|:-------------|:-----------------| + // | 1 | out pins, 2 | wait 1 irq 0 | + // | 2 | out x, 14 | wait 1 irq 0 | + // | 3 | out exec, 16 | wait 1 irq 0 | + // | 4 | | wait 1 irq 0 | + // | 5 | jmp x-- | wait 1 irq 0 | + // | 6 | | set x, 31 | + // | 7 | | out pins, 16 [4] | + // | 8 | | .. | + // | 9 | | .. | + // | 10 | | .. | + // | 11 | | .. | + // | 12 | | out pins, 16 [3] | + // | 13 | | .. | + // | 14 | | .. | + // | 15 | | .. | + // | 16 | | jump x-- loop1 | + // + // Note: Credit to + // https://gregchadwick.co.uk/blog/playing-with-the-pico-pt5/ who had a + // very similar idea to me, but wrote it up far better than I ever could. + + let timing_installed = pio.install(&timing_program.program).unwrap(); + let (mut timing_sm, _, timing_fifo) = + pico::hal::pio::PIOBuilder::from_program(timing_installed) + .buffers(pico::hal::pio::Buffers::OnlyTx) + .out_pins(0, 2) + .autopull(true) + .out_shift_direction(pico::hal::pio::ShiftDirection::Right) + .pull_threshold(32) + .build(sm0); + timing_sm.set_pindirs([ + (0, pico::hal::pio::PinDir::Output), + (1, pico::hal::pio::PinDir::Output), + ]); + + // Important notes! + // + // You must not set a clock_divider (other than 1.0) on the pixel state + // machine. You might want the pixels to be twice as wide (or mode), but + // enabling a clock divider adds a lot of jitter (i.e. the start each + // each line differs by some number of 126 MHz clock cycles). + + let pixels_installed = pio.install(&pixel_program.program).unwrap(); + let (mut pixel_sm, _, pixel_fifo) = pico::hal::pio::PIOBuilder::from_program(pixels_installed) + .buffers(pico::hal::pio::Buffers::OnlyTx) + .out_pins(2, 12) + .autopull(true) + .out_shift_direction(pico::hal::pio::ShiftDirection::Right) + .pull_threshold(32) + .build(sm1); + pixel_sm.set_pindirs((2..=13).map(|x| (x, pico::hal::pio::PinDir::Output))); + + // Read from the timing buffer and write to the timing FIFO. We get an + // IRQ when the transfer is complete (i.e. when line has been fully + // loaded). + dma.ch[TIMING_DMA_CHAN].ch_ctrl_trig.write(|w| { + w.data_size().size_word(); + w.incr_read().set_bit(); + w.incr_write().clear_bit(); + unsafe { w.treq_sel().bits(timing_fifo.dreq_value()) }; + unsafe { w.chain_to().bits(TIMING_DMA_CHAN as u8) }; + unsafe { w.ring_size().bits(0) }; + w.ring_sel().clear_bit(); + w.bswap().clear_bit(); + w.irq_quiet().clear_bit(); + w.en().set_bit(); + w.sniff_en().clear_bit(); + w + }); + dma.ch[TIMING_DMA_CHAN] + .ch_read_addr + .write(|w| unsafe { w.bits(TIMING_BUFFER.visible_line.data.as_ptr() as usize as u32) }); + dma.ch[TIMING_DMA_CHAN] + .ch_write_addr + .write(|w| unsafe { w.bits(timing_fifo.fifo_address() as usize as u32) }); + dma.ch[TIMING_DMA_CHAN] + .ch_trans_count + .write(|w| unsafe { w.bits(TIMING_BUFFER.visible_line.data.len() as u32) }); + + // Read from the pixel buffer (even first) and write to the pixel FIFO + dma.ch[PIXEL_DMA_CHAN].ch_ctrl_trig.write(|w| { + w.data_size().size_word(); + w.incr_read().set_bit(); + w.incr_write().clear_bit(); + unsafe { w.treq_sel().bits(pixel_fifo.dreq_value()) }; + unsafe { w.chain_to().bits(PIXEL_DMA_CHAN as u8) }; + unsafe { w.ring_size().bits(0) }; + w.ring_sel().clear_bit(); + w.bswap().clear_bit(); + w.irq_quiet().clear_bit(); + w.en().set_bit(); + w.sniff_en().clear_bit(); + w + }); + dma.ch[PIXEL_DMA_CHAN] + .ch_read_addr + .write(|w| unsafe { w.bits(PIXEL_DATA_BUFFER_EVEN.as_ptr()) }); + dma.ch[PIXEL_DMA_CHAN] + .ch_write_addr + .write(|w| unsafe { w.bits(pixel_fifo.fifo_address() as usize as u32) }); + dma.ch[PIXEL_DMA_CHAN] + .ch_trans_count + .write(|w| unsafe { w.bits(PIXEL_DATA_BUFFER_EVEN.pixels.len() as u32 + 1) }); + dma.inte0.write(|w| unsafe { + w.inte0() + .bits((1 << PIXEL_DMA_CHAN) | (1 << TIMING_DMA_CHAN)) + }); + + // Enable the DMA + dma.multi_chan_trigger + .write(|w| unsafe { w.bits((1 << PIXEL_DMA_CHAN) | (1 << TIMING_DMA_CHAN)) }); + + info!("DMA enabled"); + + unsafe { + // Hand off the DMA peripheral to the interrupt + DMA_PERIPH = Some(dma); + + // Enable the interrupts (DMA_PERIPH has to be set first) + cortex_m::interrupt::enable(); + pico::hal::pac::NVIC::unpend(pico::hal::pac::Interrupt::DMA_IRQ_0); + pico::hal::pac::NVIC::unmask(pico::hal::pac::Interrupt::DMA_IRQ_0); + } + + info!("IRQs enabled"); + + info!("DMA set-up complete"); + + timing_sm.start(); + pixel_sm.start(); + + info!("State Machines running"); + + // We drop our state-machine and PIO objects here - this means the video + // cannot be reconfigured at a later time, but they do keep on running + // as-is. +} + +/// Call this function whenever the DMA reports that it has completed a transfer. +/// +/// We use this as a prompt to either start a transfer or more Timing words, +/// or a transfer or more pixel words. +pub unsafe fn irq() { + let dma: &mut super::pac::DMA = match DMA_PERIPH.as_mut() { + Some(dma) => dma, + None => { + return; + } + }; + let status = dma.ints0.read().bits(); + + // Check if this is a DMA interrupt for the sync DMA channel + let timing_dma_chan_irq = (status & (1 << TIMING_DMA_CHAN)) != 0; + + // Check if this is a DMA interrupt for the line DMA channel + let pixel_dma_chan_irq = (status & (1 << PIXEL_DMA_CHAN)) != 0; + + if timing_dma_chan_irq { + // clear timing_dma_chan bit in DMA interrupt bitfield + dma.ints0.write(|w| w.bits(1 << TIMING_DMA_CHAN)); + + let old_timing_line = CURRENT_TIMING_LINE.load(Ordering::Relaxed); + let next_timing_line = if old_timing_line == V_BACK_PORCH_END { + // Wrap around + 0 + } else { + // Keep going + old_timing_line + 1 + }; + CURRENT_TIMING_LINE.store(next_timing_line, Ordering::Relaxed); + + let buffer = if next_timing_line <= V_VISIBLE_END { + // Visible lines + &TIMING_BUFFER.visible_line + } else if next_timing_line <= V_FRONT_PORCH_END { + // VGA front porch before VGA sync pulse + &TIMING_BUFFER.vblank_porch_buffer + } else if next_timing_line <= V_SYNC_END { + // Sync pulse + &TIMING_BUFFER.vblank_sync_buffer + } else { + // VGA back porch following VGA sync pulse + &TIMING_BUFFER.vblank_porch_buffer + }; + dma.ch[TIMING_DMA_CHAN] + .ch_al3_read_addr_trig + .write(|w| w.bits(buffer as *const _ as usize as u32)) + } + + if pixel_dma_chan_irq { + dma.ints0.write(|w| w.bits(1 << PIXEL_DMA_CHAN)); + + // A pixel DMA transfer is now complete. This only fires on visible lines. + + let mut next_display_line = CURRENT_DISPLAY_LINE.load(Ordering::Relaxed) + 1; + if next_display_line > V_VISIBLE_END { + next_display_line = 0; + }; + + // Set the DMA load address according to which line we are on. We use + // the 'trigger' alias to restart the DMA at the same time as we + // write the new read address. The DMA had stopped because the + // previous line was transferred completely. + if (next_display_line & 1) == 1 { + // Odd visible line is next + dma.ch[PIXEL_DMA_CHAN] + .ch_al3_read_addr_trig + .write(|w| w.bits(PIXEL_DATA_BUFFER_ODD.as_ptr())) + } else { + // Even visible line is next + dma.ch[PIXEL_DMA_CHAN] + .ch_al3_read_addr_trig + .write(|w| w.bits(PIXEL_DATA_BUFFER_EVEN.as_ptr())) + } + + CURRENT_DISPLAY_LINE.store(next_display_line, Ordering::Relaxed); + DMA_READY.store(true, Ordering::Relaxed); + } +} + +/// Generate a 32-bit value we can send to the Timing FIFO. +/// +/// * `period` - The length of this portion of the scan-line, in system clock ticks +/// * `hsync` - true if the H-Sync pin should be high during this period, else false +/// * `vsync` - true if the H-Sync pin should be high during this period, else false +/// * `raise_irq` - true the timing statemachine should raise an IRQ at the start of this period +/// +/// Returns a 32-bit value you can post to the Timing FIFO. +const fn make_timing(period: u32, hsync: bool, vsync: bool, raise_irq: bool) -> u32 { + let command = if raise_irq { + // This command sets IRQ 0. It is the same as: + // + // ``` + // pio::InstructionOperands::IRQ { + // clear: false, + // wait: false, + // index: 0, + // relative: false, + // }.encode() + // ``` + // + // Unfortunately encoding this isn't a const-fn, so we cheat: + 0xc000 + } else { + // This command is a no-op (it moves Y into Y) + // + // ``` + // pio::InstructionOperands::MOV { + // destination: pio::MovDestination::Y, + // op: pio::MovOperation::None, + // source: pio::MovSource::Y, + // }.encode() + // ``` + // + // Unfortunately encoding this isn't a const-fn, so we cheat: + 0xa042 + }; + let mut value: u32 = 0; + if hsync { + value |= 1 << 0; + } + if vsync { + value |= 1 << 1; + } + value |= (period - 6) << 2; + value | command << 16 +} + +impl LineBuffer { + /// Convert the line buffer to a 32-bit address that the DMA engine understands. + fn as_ptr(&self) -> u32 { + self as *const _ as usize as u32 + } +} + +// ----------------------------------------------------------------------------- +// End of file +// ----------------------------------------------------------------------------- From 7a4bc1b038f040be748883584692bb5684d0953a Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Thu, 18 Nov 2021 15:44:31 +0000 Subject: [PATCH 28/41] Tweak the timing back to spec. --- src/vga/mod.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/vga/mod.rs b/src/vga/mod.rs index 6c0f9ff..bc31fd9 100644 --- a/src/vga/mod.rs +++ b/src/vga/mod.rs @@ -196,11 +196,13 @@ static TIMING_BUFFER: TimingBuffer = TimingBuffer { make_timing(16 * 5, true, false, false), // Sync pulse (as per the spec) make_timing(96 * 5, false, false, false), - // Back porch (shortened by two pixels because the - // video starts two pixels late) - make_timing(48 * 5, true, false, false), - // Visible portion. It also triggers the IRQ to start pixels moving. - make_timing(640 * 5, true, false, true), + // Back porch. Adjusted by a few clocks to account for interrupt + + // PIO SM start latency. + make_timing((48 * 5) - 5, true, false, false), + // Visible portion. It also triggers the IRQ to start pixels + // moving. Adjusted to compensate for changes made to previous + // period to ensure scan-line remains at correct length. + make_timing((640 * 5) + 5, true, false, true), ], }, vblank_porch_buffer: ScanlineTimingBuffer { @@ -493,9 +495,9 @@ pub fn init(pio: super::pac::PIO0, dma: super::pac::DMA, resets: &mut super::pac // | 1 | out pins, 2 | wait 1 irq 0 | // | 2 | out x, 14 | wait 1 irq 0 | // | 3 | out exec, 16 | wait 1 irq 0 | - // | 4 | | wait 1 irq 0 | + // | 4 | | wait 1 irq 0 | // | 5 | jmp x-- | wait 1 irq 0 | - // | 6 | | set x, 31 | + // | 6 | | out x, 32 | // | 7 | | out pins, 16 [4] | // | 8 | | .. | // | 9 | | .. | @@ -505,7 +507,7 @@ pub fn init(pio: super::pac::PIO0, dma: super::pac::DMA, resets: &mut super::pac // | 13 | | .. | // | 14 | | .. | // | 15 | | .. | - // | 16 | | jump x-- loop1 | + // | 16 | | jump x-- | // // Note: Credit to // https://gregchadwick.co.uk/blog/playing-with-the-pico-pt5/ who had a From 750babc5b5da647cbd6f81cba9ef60da3246f0a1 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Thu, 18 Nov 2021 15:44:39 +0000 Subject: [PATCH 29/41] Clippy fixes --- src/vga/mod.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/vga/mod.rs b/src/vga/mod.rs index bc31fd9..c0b3687 100644 --- a/src/vga/mod.rs +++ b/src/vga/mod.rs @@ -391,7 +391,7 @@ impl VideoEngine { // Convert from eight mono pixels in one byte to four RGB pairs unsafe { core::ptr::write_volatile( - scan_line_buffer_ptr.offset(px_idx + 0), + scan_line_buffer_ptr.offset(px_idx), self.lookup[(mono_pixels >> 6) & 3], ); core::ptr::write_volatile( @@ -404,7 +404,7 @@ impl VideoEngine { ); core::ptr::write_volatile( scan_line_buffer_ptr.offset(px_idx + 3), - self.lookup[(mono_pixels >> 0) & 3], + self.lookup[mono_pixels & 3], ); } px_idx += 4; @@ -416,6 +416,12 @@ impl VideoEngine { } } +impl Default for VideoEngine { + fn default() -> Self { + VideoEngine::new() + } +} + /// Initialise all the static data and peripherals we need for our video display. /// /// We need to keep `pio` and `dma` to run the video. We need `resets` to set @@ -634,6 +640,10 @@ pub fn init(pio: super::pac::PIO0, dma: super::pac::DMA, resets: &mut super::pac /// /// We use this as a prompt to either start a transfer or more Timing words, /// or a transfer or more pixel words. +/// +/// # Safety +/// +/// Only call this from the DMA IRQ handler. pub unsafe fn irq() { let dma: &mut super::pac::DMA = match DMA_PERIPH.as_mut() { Some(dma) => dma, From c0a4bc120cb6ffb019ec7a35fc4a0e5ee8fdca4a Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Fri, 19 Nov 2021 17:17:52 +0000 Subject: [PATCH 30/41] Move the rendering over to Core 1. Requires a bunch of patches to the HAL. --- src/main.rs | 24 +- src/vga/mod.rs | 625 ++++++++++++++++++++++++++++++++----------------- 2 files changed, 425 insertions(+), 224 deletions(-) diff --git a/src/main.rs b/src/main.rs index 60920f5..4aa8695 100644 --- a/src/main.rs +++ b/src/main.rs @@ -154,7 +154,7 @@ fn main() -> ! { // sio is the *Single-cycle Input/Output* peripheral. It has all our GPIO // pins, as well as some mailboxes and other useful things for inter-core // communications. - let sio = hal::sio::Sio::new(pac.SIO); + let mut sio = hal::sio::Sio::new(pac.SIO); // Configure and grab all the RP2040 pins the Pico exposes. let pins = pico::Pins::new( @@ -186,24 +186,22 @@ fn main() -> ! { let _blue2 = pins.gpio12.into_mode::(); let _blue3 = pins.gpio13.into_mode::(); - vga::init(pac.PIO0, pac.DMA, &mut pac.RESETS); - - core.SYST - .set_clock_source(cortex_m::peripheral::syst::SystClkSource::External); - core.SYST.set_reload(0x00FF_FFFF); - core.SYST.clear_current(); - core.SYST.enable_counter(); - - let mut video = vga::VideoEngine::new(); + vga::init( + pac.PIO0, + pac.DMA, + &mut pac.RESETS, + &mut pac.PPB, + &mut sio.fifo, + &mut pac.PSM, + ); loop { cortex_m::asm::wfi(); - // This function currently consumes about 70% CPU (or rather, 90% CPU - // on each of 400 lines, and 0% CPU on the other 50 lines) - video.poll(); } } +/// Called when DMA raises IRQ0; i.e. when a DMA transfer to the pixel FIFO or +/// the timing FIFO has completed. #[interrupt] fn DMA_IRQ_0() { unsafe { diff --git a/src/vga/mod.rs b/src/vga/mod.rs index c0b3687..eab4c36 100644 --- a/src/vga/mod.rs +++ b/src/vga/mod.rs @@ -39,7 +39,7 @@ pub(crate) mod font; // Imports // ----------------------------------------------------------------------------- -use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; +use core::sync::atomic::{AtomicBool, AtomicU16, Ordering}; use defmt::*; use pico::hal::pio::PIOExt; @@ -54,19 +54,31 @@ use pico::hal::pio::PIOExt; pub struct VideoEngine { /// How many frames have been drawn frame_count: u32, - // How many ticks the CPU spent drawing, across the entire frame. - running_ticks: u32, /// Look-up table mapping two 1-bpp pixels to two 12-bit RGB values (packed into one 32-bit word). /// /// You can adjust this table to convert text to different colours. lookup: [RGBPair; 4], } +/// Describes one scan-line's worth of pixels, including the length word required by the Pixel FIFO. #[repr(C, align(16))] struct LineBuffer { + /// Must be one less than the number of pixel-pairs in `pixels` length: u32, - pixels: [RGBPair; NUM_PIXEL_PAIRS_PER_LINE], + /// Pixels to be displayed, grouped into pairs (to save FIFO space and reduce DMA bandwidth) + pixels: [RGBPair; MAX_NUM_PIXEL_PAIRS_PER_LINE], } + +/// Describes the polarity of a sync pulse. +/// +/// Some pulses are positive (active-high), some are negative (active-low). +pub enum SyncPolarity { + /// An active-high pulse + Positive, + /// An active-low pulse + Negative, +} + /// Holds the four scan-line timing FIFO words we need for one scan-line. /// /// See `make_timing` for a function which can generate these words. We DMA @@ -78,7 +90,6 @@ struct ScanlineTimingBuffer { /// Holds the different kinds of scan-line timing buffers we need for various /// portions of the screen. -#[repr(C, align(16))] struct TimingBuffer { /// We use this when there are visible pixels on screen visible_line: ScanlineTimingBuffer, @@ -86,6 +97,14 @@ struct TimingBuffer { vblank_porch_buffer: ScanlineTimingBuffer, /// We use this during the v-sync sync pulse vblank_sync_buffer: ScanlineTimingBuffer, + /// The last visible scan-line, + visible_lines_ends_at: u16, + /// The last scan-line of the front porch + front_porch_end_at: u16, + /// The last scan-line of the sync pulse + sync_pulse_ends_at: u16, + /// The last scan-line of the back-porch (and the frame) + back_porch_ends_at: u16, } /// Represents a 12-bit colour value. @@ -97,15 +116,6 @@ struct TimingBuffer { #[derive(Copy, Clone, PartialEq, Eq)] pub struct RGBColour(u16); -impl RGBColour { - pub const fn from_24bit(red: u8, green: u8, blue: u8) -> RGBColour { - let red: u16 = (red as u16) & 0x00F; - let green: u16 = (green as u16) & 0x00F; - let blue: u16 = (blue as u16) & 0x00F; - RGBColour((blue << 12) | (green << 4) | red) - } -} - /// Represents two `RGBColour` pixels packed together. /// /// The `first` pixel is packed in the lower 16-bits. This is because the PIO @@ -114,14 +124,6 @@ impl RGBColour { #[derive(Copy, Clone, PartialEq, Eq)] pub struct RGBPair(u32); -impl RGBPair { - pub const fn from_pixels(first: RGBColour, second: RGBColour) -> RGBPair { - let first: u32 = first.0 as u32; - let second: u32 = second.0 as u32; - RGBPair((second << 16) | first) - } -} - // ----------------------------------------------------------------------------- // Static and Const Data // ----------------------------------------------------------------------------- @@ -131,111 +133,34 @@ impl RGBPair { /// Adjust the pixel PIO program to run at the right speed to the screen is /// filled. For example, if this is only 320 but you are aiming at 640x480, /// make the pixel PIO take twice as long per pixel. -const NUM_PIXELS_PER_LINE: usize = 640; +const MAX_NUM_PIXELS_PER_LINE: usize = 640; /// How many pixel pairs we send out. /// /// Each pixel is two 12-bit values packed into one 32-bit word(an `RGBPair`). /// This is to make more efficient use of DMA and FIFO resources. -const NUM_PIXEL_PAIRS_PER_LINE: usize = NUM_PIXELS_PER_LINE / 2; +const MAX_NUM_PIXEL_PAIRS_PER_LINE: usize = MAX_NUM_PIXELS_PER_LINE / 2; -/// Number of lines on screen. -const NUM_LINES: usize = 400; +/// Maximum number of lines on screen. +const MAX_NUM_LINES: usize = 480; /// The highest number of columns in any text mode. -pub const NUM_TEXT_COLS: usize = NUM_PIXELS_PER_LINE / font::WIDTH_PX; +pub const NUM_TEXT_COLS: usize = MAX_NUM_PIXELS_PER_LINE / font::WIDTH_PX; /// The highest number of rows in any text mode. -pub const NUM_TEXT_ROWS: usize = NUM_LINES as usize / font::HEIGHT_PX; +pub const NUM_TEXT_ROWS: usize = MAX_NUM_LINES as usize / font::HEIGHT_PX; -/// Scan-line on which the visible portion of the screen ends. -/// -/// This value is for 400 line, 70 Hz mode. We assume the visible portion starts on line zero. -const V_VISIBLE_END: usize = V_FRONT_PORCH_START - 1; - -/// Scan-line on which the front-porch starts. -/// -/// This value is for 400 line, 70 Hz mode. -const V_FRONT_PORCH_START: usize = NUM_LINES; - -/// Scan-line on which the front-porch ends. -/// -/// This value is for 400 line, 70 Hz mode. -const V_FRONT_PORCH_END: usize = V_SYNC_START - 1; - -/// Scan-line on which the sync-pulse starts. -/// -/// This value is for 400 line, 70 Hz mode. -const V_SYNC_START: usize = 412; - -/// Scan-line on which the sync pulse ends. -/// -/// This value is for 400 line, 70 Hz mode. -const V_SYNC_END: usize = V_BACK_PORCH_START - 1; - -/// Scan-line on which the back-porch starts. -/// -/// This value is for 400 line, 70 Hz mode. -const V_BACK_PORCH_START: usize = 416; - -/// Scan-line on which the back-porch ends. -/// -/// This value is for 400 line, 70 Hz mode. -const V_BACK_PORCH_END: usize = 449; +/// Used to signal when Core 1 has started +static CORE1_START_FLAG: AtomicBool = AtomicBool::new(false); /// Stores our timing data which we DMA into the timing PIO State Machine -static TIMING_BUFFER: TimingBuffer = TimingBuffer { - // Note - the order of the arguments to `make_timing` is: - // - // * H-Sync Pulse (true = high, false = low) - // * V-Sync Pulse (true = high, false = low) - // * Generate pixel start IRQ (true or false) - visible_line: ScanlineTimingBuffer { - data: [ - // Front porch (as per the spec) - make_timing(16 * 5, true, false, false), - // Sync pulse (as per the spec) - make_timing(96 * 5, false, false, false), - // Back porch. Adjusted by a few clocks to account for interrupt + - // PIO SM start latency. - make_timing((48 * 5) - 5, true, false, false), - // Visible portion. It also triggers the IRQ to start pixels - // moving. Adjusted to compensate for changes made to previous - // period to ensure scan-line remains at correct length. - make_timing((640 * 5) + 5, true, false, true), - ], - }, - vblank_porch_buffer: ScanlineTimingBuffer { - data: [ - // Front porch - make_timing(16 * 5, true, false, false), - // Sync pulse - make_timing(96 * 5, false, false, false), - // Back porch - make_timing(48 * 5, true, false, false), - // 'visible' portion (but it's blank) - make_timing(640 * 5, true, false, false), - ], - }, - vblank_sync_buffer: ScanlineTimingBuffer { - data: [ - // Front porch - make_timing(16 * 5, true, true, false), - // Sync pulse - make_timing(96 * 5, false, true, false), - // Back porch - make_timing(48 * 5, true, true, false), - // Visible portion (but it's blank) - make_timing(640 * 5, true, true, false), - ], - }, -}; +static TIMING_BUFFER: TimingBuffer = TimingBuffer::make_640x480(); /// Tracks which scan-line we are currently on (for timing purposes => it goes 0..=449) -static CURRENT_TIMING_LINE: AtomicUsize = AtomicUsize::new(0); +static CURRENT_TIMING_LINE: AtomicU16 = AtomicU16::new(0); /// Tracks which scan-line we are currently on (for pixel purposes => it goes 0..NUM_LINES) -static CURRENT_DISPLAY_LINE: AtomicUsize = AtomicUsize::new(0); +static CURRENT_DISPLAY_LINE: AtomicU16 = AtomicU16::new(0); /// Set to `true` when DMA of previous line is complete and next line is scheduled. static DMA_READY: AtomicBool = AtomicBool::new(false); @@ -251,14 +176,14 @@ const PIXEL_DMA_CHAN: usize = 1; /// 12-bit pixels for the even scan-lines (0, 2, 4 ... NUM_LINES - 2). Defaults to black. static mut PIXEL_DATA_BUFFER_EVEN: LineBuffer = LineBuffer { - length: (NUM_PIXEL_PAIRS_PER_LINE as u32) - 1, - pixels: [RGBPair::from_pixels(colours::WHITE, colours::BLACK); NUM_PIXEL_PAIRS_PER_LINE], + length: (MAX_NUM_PIXEL_PAIRS_PER_LINE as u32) - 1, + pixels: [RGBPair::from_pixels(colours::WHITE, colours::BLACK); MAX_NUM_PIXEL_PAIRS_PER_LINE], }; /// 12-bit pixels for the odd scan-lines (1, 3, 5 ... NUM_LINES-1). Defaults to white. static mut PIXEL_DATA_BUFFER_ODD: LineBuffer = LineBuffer { - length: (NUM_PIXEL_PAIRS_PER_LINE as u32) - 1, - pixels: [RGBPair::from_pixels(colours::BLACK, colours::WHITE); NUM_PIXEL_PAIRS_PER_LINE], + length: (MAX_NUM_PIXEL_PAIRS_PER_LINE as u32) - 1, + pixels: [RGBPair::from_pixels(colours::BLACK, colours::WHITE); MAX_NUM_PIXEL_PAIRS_PER_LINE], }; /// Some dummy text to display. @@ -266,32 +191,7 @@ static mut PIXEL_DATA_BUFFER_ODD: LineBuffer = LineBuffer { /// This is arranged as `NUM_TEXT_ROWS` rows of `NUM_TEXT_COLS` columns. Each /// item is an index into `font::FONT_DATA` (or a Code-Page 850 character). static mut CHAR_ARRAY: [u8; NUM_TEXT_COLS as usize * NUM_TEXT_ROWS as usize] = -*b"\ -\xDA\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xBF\ -\xB3Neotron Pico Booting... \xB3\ -\xB3 \xB3\ -\xB3 \xB3\ -\xB3 \xB3\ -\xB3 \xB3\ -\xB3 \xDb\xDb\xDb\xBB\xB2\xB2\xDb\xDb\xBB\xDb\xDb\xDb\xDb\xDb\xDb\xDb\xBB\xB2\xDb\xDb\xDb\xDb\xDb\xBB\xB2\xDb\xDb\xDb\xDb\xDb\xDb\xDb\xDb\xBB\xDb\xDb\xDb\xDb\xDb\xDb\xBB\xB2\xB2\xDb\xDb\xDb\xDb\xDb\xBB\xB2\xDb\xDb\xDb\xBB\xB2\xB2\xDb\xDb\xBB \xB3\ -\xB3 \xDb\xDb\xDb\xDb\xBB\xB2\xDb\xDb\xBA\xDb\xDb\xC9\xCD\xCD\xCD\xCD\xBC\xDb\xDb\xC9\xCD\xCD\xDb\xDb\xBB\xC8\xCD\xCD\xDb\xDb\xC9\xCD\xCD\xBC\xDb\xDb\xC9\xCD\xCD\xDb\xDb\xBB\xDb\xDb\xC9\xCD\xCD\xDb\xDb\xBB\xDb\xDb\xDb\xDb\xBB\xB2\xDb\xDb\xBA \xB3\ -\xB3 \xDb\xDb\xC9\xDb\xDb\xBB\xDb\xDb\xBA\xDb\xDb\xDb\xDb\xDb\xBB\xB2\xB2\xDb\xDb\xBA\xB2\xB2\xDb\xDb\xBA\xB2\xB2\xB2\xDb\xDb\xBA\xB2\xB2\xB2\xDb\xDb\xDb\xDb\xDb\xDb\xC9\xBC\xDb\xDb\xBA\xB2\xB2\xDb\xDb\xBA\xDb\xDb\xC9\xDb\xDb\xBB\xDb\xDb\xBA \xB3\ -\xB3 \xDb\xDb\xBA\xC8\xDb\xDb\xDb\xDb\xBA\xDb\xDb\xC9\xCD\xCD\xBC\xB2\xB2\xDb\xDb\xBA\xB2\xB2\xDb\xDb\xBA\xB2\xB2\xB2\xDb\xDb\xBA\xB2\xB2\xB2\xDb\xDb\xC9\xCD\xCD\xDb\xDb\xBB\xDb\xDb\xBA\xB2\xB2\xDb\xDb\xBA\xDb\xDb\xBA\xC8\xDb\xDb\xDb\xDb\xBA \xB3\ -\xB3 \xDb\xDb\xBA\xB2\xC8\xDb\xDb\xDb\xBA\xDb\xDb\xDb\xDb\xDb\xDb\xDb\xBB\xC8\xDb\xDb\xDb\xDb\xDb\xC9\xBC\xB2\xB2\xB2\xDb\xDb\xBA\xB2\xB2\xB2\xDb\xDb\xBA\xB2\xB2\xDb\xDb\xBA\xC8\xDb\xDb\xDb\xDb\xDb\xC9\xBC\xDb\xDb\xBA\xB2\xC8\xDb\xDb\xDb\xBA \xB3\ -\xB3 \xB3\ -\xB3 \xB3\ -\xB3 \xB3\ -\xB3 \xB3\ -\xB3 \xB3\ -\xB3 \xB3\ -\xB3 \xB3\ -\xB3 \xB3\ -\xB3 \xB3\ -\xB3 \xB3\ -\xB3 \xB3\ -\xB3 \xB3\ -\xB3This is the end \xB3\ -\xC0\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xD9"; + [0xB2; NUM_TEXT_COLS as usize * NUM_TEXT_ROWS as usize]; /// A set of useful constants representing common RGB colours. pub mod colours { @@ -320,7 +220,6 @@ impl VideoEngine { pub fn new() -> VideoEngine { VideoEngine { frame_count: 0, - running_ticks: 0, lookup: [ RGBPair::from_pixels(colours::BLUE, colours::BLUE), RGBPair::from_pixels(colours::BLUE, colours::WHITE), @@ -332,16 +231,13 @@ impl VideoEngine { pub fn poll(&mut self) { if DMA_READY.load(Ordering::Relaxed) { - let awake_at = cortex_m::peripheral::SYST::get_current(); DMA_READY.store(false, Ordering::Relaxed); + unsafe { + CHAR_ARRAY[0] = CHAR_ARRAY[0].wrapping_add(1); + } let current_line_num = CURRENT_DISPLAY_LINE.load(Ordering::Relaxed); if current_line_num == 0 { - let cpu_percent = ((self.running_ticks * 1000 * 100) / 400) / 31_777; - info!( - "Frame {}, awake {} ticks ({}% load)", - self.frame_count, self.running_ticks, cpu_percent - ); - self.running_ticks = 0; + info!("Frame {}", self.frame_count); self.frame_count += 1; } @@ -355,8 +251,8 @@ impl VideoEngine { }; // Convert our position in scan-lines to a text row, and a line within each glyph on that row - let text_row = (current_line_num / font::HEIGHT_PX) as usize; - let font_row = (current_line_num % font::HEIGHT_PX) as usize; + let text_row = (current_line_num / font::HEIGHT_PX as u16) as usize; + let font_row = (current_line_num % font::HEIGHT_PX as u16) as usize; if text_row < NUM_TEXT_ROWS { // Note (unsafe): We could stash the char array inside `self` @@ -410,8 +306,6 @@ impl VideoEngine { px_idx += 4; } } - let running_end = cortex_m::peripheral::SYST::get_current(); - self.running_ticks += (awake_at - running_end) & 0x00FF_FFFF; } } } @@ -426,7 +320,14 @@ impl Default for VideoEngine { /// /// We need to keep `pio` and `dma` to run the video. We need `resets` to set /// things up, so we only borrow that. -pub fn init(pio: super::pac::PIO0, dma: super::pac::DMA, resets: &mut super::pac::RESETS) { +pub fn init( + pio: super::pac::PIO0, + dma: super::pac::DMA, + resets: &mut super::pac::RESETS, + ppb: &mut pico::hal::pac::PPB, + fifo: &mut pico::hal::sio::SioFifo, + psm: &mut pico::hal::pac::PSM, +) { // Grab PIO0 and the state machines it contains let (mut pio, sm0, sm1, _sm2, _sm3) = pio.split(resets); @@ -482,9 +383,9 @@ pub fn init(pio: super::pac::PIO0, dma: super::pac::DMA, resets: &mut super::pac ; Read the line length (in pixel-pairs) out x, 32 loop1: - ; Write out first pixel - takes 5 clocks per pixel (double-width) + ; Write out first pixel - takes 5 clocks per pixel out pins, 16 [4] - ; Write out second pixel - takes 5 clocks per pixel (double-width, allowing one for the jump) + ; Write out second pixel - takes 5 clocks per pixel (allowing one clock for the jump) out pins, 16 [3] ; Repeat until all pixel pairs sent jmp x-- loop1 @@ -523,7 +424,7 @@ pub fn init(pio: super::pac::PIO0, dma: super::pac::DMA, resets: &mut super::pac let (mut timing_sm, _, timing_fifo) = pico::hal::pio::PIOBuilder::from_program(timing_installed) .buffers(pico::hal::pio::Buffers::OnlyTx) - .out_pins(0, 2) + .out_pins(0, 2) // H-Sync is GPIO0, V-Sync is GPIO1 .autopull(true) .out_shift_direction(pico::hal::pio::ShiftDirection::Right) .pull_threshold(32) @@ -543,10 +444,10 @@ pub fn init(pio: super::pac::PIO0, dma: super::pac::DMA, resets: &mut super::pac let pixels_installed = pio.install(&pixel_program.program).unwrap(); let (mut pixel_sm, _, pixel_fifo) = pico::hal::pio::PIOBuilder::from_program(pixels_installed) .buffers(pico::hal::pio::Buffers::OnlyTx) - .out_pins(2, 12) + .out_pins(2, 12) // Red0 is GPIO2, Blue3 is GPIO13 .autopull(true) .out_shift_direction(pico::hal::pio::ShiftDirection::Right) - .pull_threshold(32) + .pull_threshold(32) // We read all 32-bits in each FIFO word .build(sm1); pixel_sm.set_pindirs((2..=13).map(|x| (x, pico::hal::pio::PinDir::Output))); @@ -634,6 +535,146 @@ pub fn init(pio: super::pac::PIO0, dma: super::pac::DMA, resets: &mut super::pac // We drop our state-machine and PIO objects here - this means the video // cannot be reconfigured at a later time, but they do keep on running // as-is. + + static mut CORE1_STACK: [usize; 1024] = [0usize; 1024]; + + unsafe { + multicore_launch_core1_with_stack(core1_main, &mut CORE1_STACK, ppb, fifo, psm); + } + + info!("Core 1 running"); +} + +/// The bootrom code will call this function on core1 to perform any set-up, before the +/// entry function is called. +extern "C" fn core1_wrapper(entry_func: extern "C" fn() -> u32, _stack_base: *mut u32) -> u32 { + return entry_func(); +} + +/// Core 1 entry function. +/// +/// This is a naked function I have pre-compiled to thumb-2 instructions. I +/// could use inline assembler, but then I'd have to make you install +/// arm-none-eabi-as or arm-none-eabi-gcc, or wait until `llvm_asm!` is +/// stablised. +static CORE1_ENTRY_FUNCTION: [u16; 2] = [ + // pop {r0, r1, pc} - load the three parameters (which we placed in Core 1's stack) into registers. + // Loading PC is basically a jump. + 0xbd03, // nop - pad this out to 32-bits long + 0x46c0, +]; + +/// Starts core 1 running the given function, with the given stack. +fn multicore_launch_core1_with_stack( + main_func: unsafe extern "C" fn() -> u32, + stack: &mut [usize], + ppb: &mut pico::hal::pac::PPB, + fifo: &mut pico::hal::sio::SioFifo, + psm: &mut pico::hal::pac::PSM, +) { + info!("Resetting CPU1..."); + + psm.frce_off.modify(|_, w| w.proc1().set_bit()); + while !psm.frce_off.read().proc1().bit_is_set() { + cortex_m::asm::nop(); + } + psm.frce_off.modify(|_, w| w.proc1().clear_bit()); + + info!("Setting up stack..."); + + // Gets popped into r0 by CORE1_ENTRY_FUNCTION. This is the function we + // want to run. It appears in `core1_wrapper` as the first argument. + stack[stack.len() - 3] = main_func as *const () as usize; + // Gets popped into r1 by CORE1_ENTRY_FUNCTION. This is the top of stack + // for Core 1. It appears in `core1_wrapper` as the second argument. + stack[stack.len() - 2] = stack.as_ptr() as *const _ as usize; + // Gets popped into pc by CORE1_ENTRY_FUNCTION. This is the function + // `CORE1_ENTRY_FUNCTION` will jump to, passing the above two values as + // arguments. + stack[stack.len() - 1] = core1_wrapper as *const () as usize; + // Point into the top of the stack (so there are three values pushed onto it, i.e. at/above it) + let stack_ptr = unsafe { stack.as_mut_ptr().add(stack.len() - 3) }; + + info!("Stack ptr is 0x{:x}", stack_ptr); + info!("Stack bottom is 0x{:x}", stack.as_ptr()); + info!("Stack top is 0x{:x}", &stack[stack.len() - 4..stack.len()]); + + // This is the launch sequence we send to core1, to get it to leave the + // boot ROM and run our code. + let cmd_sequence: [u32; 6] = [ + 0, + 0, + 1, + ppb.vtor.read().bits() as usize as u32, + stack_ptr as usize as u32, + // Have to add 1 to convert from an array pointer to a thumb instruction pointer + (CORE1_ENTRY_FUNCTION.as_ptr() as usize as u32) + 1, + ]; + + let enabled = pico::hal::pac::NVIC::is_enabled(pico::hal::pac::Interrupt::SIO_IRQ_PROC0); + pico::hal::pac::NVIC::mask(pico::hal::pac::Interrupt::SIO_IRQ_PROC0); + + 'outer: loop { + for cmd in cmd_sequence.iter() { + info!("Sending command {:x}...", *cmd); + + // we drain before sending a 0 + if *cmd == 0 { + info!("Draining FIFO..."); + fifo.drain(); + // core 1 may be waiting for fifo space + cortex_m::asm::sev(); + } + info!("Pushing to FIFO..."); + fifo.write_blocking(*cmd); + + info!("Getting response from FIFO..."); + let response = loop { + if let Some(x) = fifo.read() { + break x; + } else { + info!("ST is {:x}", fifo.status()); + } + }; + + // move to next state on correct response otherwise start over + info!("Got {:x}", response); + if *cmd != response { + continue 'outer; + } + } + break; + } + + if enabled { + unsafe { pico::hal::pac::NVIC::unmask(pico::hal::pac::Interrupt::SIO_IRQ_PROC0) }; + } + + info!("Waiting for Core 1 to start..."); + while !CORE1_START_FLAG.load(Ordering::Relaxed) { + cortex_m::asm::nop(); + } + info!("Core 1 started!!"); +} + +/// This function runs the video processing loop on Core 1. +/// +/// It keeps the odd/even scan-line buffers updated, as per the contents of +/// the text buffer. +/// +/// # Safety +/// +/// Only run this function on Core 1. +unsafe extern "C" fn core1_main() -> u32 { + CORE1_START_FLAG.store(true, Ordering::Relaxed); + + let mut video = VideoEngine::new(); + + loop { + // This function currently consumes about 70% CPU (or rather, 90% CPU + // on each of 400 lines, and 0% CPU on the other 50 lines) + video.poll(); + } } /// Call this function whenever the DMA reports that it has completed a transfer. @@ -664,7 +705,7 @@ pub unsafe fn irq() { dma.ints0.write(|w| w.bits(1 << TIMING_DMA_CHAN)); let old_timing_line = CURRENT_TIMING_LINE.load(Ordering::Relaxed); - let next_timing_line = if old_timing_line == V_BACK_PORCH_END { + let next_timing_line = if old_timing_line == TIMING_BUFFER.back_porch_ends_at { // Wrap around 0 } else { @@ -673,13 +714,13 @@ pub unsafe fn irq() { }; CURRENT_TIMING_LINE.store(next_timing_line, Ordering::Relaxed); - let buffer = if next_timing_line <= V_VISIBLE_END { + let buffer = if next_timing_line <= TIMING_BUFFER.visible_lines_ends_at { // Visible lines &TIMING_BUFFER.visible_line - } else if next_timing_line <= V_FRONT_PORCH_END { + } else if next_timing_line <= TIMING_BUFFER.front_porch_end_at { // VGA front porch before VGA sync pulse &TIMING_BUFFER.vblank_porch_buffer - } else if next_timing_line <= V_SYNC_END { + } else if next_timing_line <= TIMING_BUFFER.sync_pulse_ends_at { // Sync pulse &TIMING_BUFFER.vblank_sync_buffer } else { @@ -697,7 +738,7 @@ pub unsafe fn irq() { // A pixel DMA transfer is now complete. This only fires on visible lines. let mut next_display_line = CURRENT_DISPLAY_LINE.load(Ordering::Relaxed) + 1; - if next_display_line > V_VISIBLE_END { + if next_display_line > TIMING_BUFFER.visible_lines_ends_at { next_display_line = 0; }; @@ -722,54 +763,6 @@ pub unsafe fn irq() { } } -/// Generate a 32-bit value we can send to the Timing FIFO. -/// -/// * `period` - The length of this portion of the scan-line, in system clock ticks -/// * `hsync` - true if the H-Sync pin should be high during this period, else false -/// * `vsync` - true if the H-Sync pin should be high during this period, else false -/// * `raise_irq` - true the timing statemachine should raise an IRQ at the start of this period -/// -/// Returns a 32-bit value you can post to the Timing FIFO. -const fn make_timing(period: u32, hsync: bool, vsync: bool, raise_irq: bool) -> u32 { - let command = if raise_irq { - // This command sets IRQ 0. It is the same as: - // - // ``` - // pio::InstructionOperands::IRQ { - // clear: false, - // wait: false, - // index: 0, - // relative: false, - // }.encode() - // ``` - // - // Unfortunately encoding this isn't a const-fn, so we cheat: - 0xc000 - } else { - // This command is a no-op (it moves Y into Y) - // - // ``` - // pio::InstructionOperands::MOV { - // destination: pio::MovDestination::Y, - // op: pio::MovOperation::None, - // source: pio::MovSource::Y, - // }.encode() - // ``` - // - // Unfortunately encoding this isn't a const-fn, so we cheat: - 0xa042 - }; - let mut value: u32 = 0; - if hsync { - value |= 1 << 0; - } - if vsync { - value |= 1 << 1; - } - value |= (period - 6) << 2; - value | command << 16 -} - impl LineBuffer { /// Convert the line buffer to a 32-bit address that the DMA engine understands. fn as_ptr(&self) -> u32 { @@ -777,6 +770,216 @@ impl LineBuffer { } } +impl SyncPolarity { + const fn enabled(&self) -> bool { + match self { + SyncPolarity::Positive => true, + SyncPolarity::Negative => false, + } + } + + const fn disabled(&self) -> bool { + match self { + SyncPolarity::Positive => false, + SyncPolarity::Negative => true, + } + } +} + +impl ScanlineTimingBuffer { + /// Create a timing buffer for each scan-line in the V-Sync visible portion. + /// + /// The timings are in the order (front-porch, sync, back-porch, visible) and are in pixel clocks. + const fn new_v_visible( + hsync: SyncPolarity, + vsync: SyncPolarity, + timings: (u32, u32, u32, u32), + ) -> ScanlineTimingBuffer { + ScanlineTimingBuffer { + data: [ + // Front porch (as per the spec) + Self::make_timing(timings.0 * 5, hsync.disabled(), vsync.disabled(), false), + // Sync pulse (as per the spec) + Self::make_timing(timings.1 * 5, hsync.enabled(), vsync.disabled(), false), + // Back porch. Adjusted by a few clocks to account for interrupt + + // PIO SM start latency. + Self::make_timing( + (timings.2 * 5) - 5, + hsync.disabled(), + vsync.disabled(), + false, + ), + // Visible portion. It also triggers the IRQ to start pixels + // moving. Adjusted to compensate for changes made to previous + // period to ensure scan-line remains at correct length. + Self::make_timing( + (timings.3 * 5) + 5, + hsync.disabled(), + vsync.disabled(), + true, + ), + ], + } + } + + /// Create a timing buffer for each scan-line in the V-Sync front-porch and back-porch + const fn new_v_porch( + hsync: SyncPolarity, + vsync: SyncPolarity, + timings: (u32, u32, u32, u32), + ) -> ScanlineTimingBuffer { + ScanlineTimingBuffer { + data: [ + // Front porch (as per the spec) + Self::make_timing(timings.0 * 5, hsync.disabled(), vsync.disabled(), false), + // Sync pulse (as per the spec) + Self::make_timing(timings.1 * 5, hsync.enabled(), vsync.disabled(), false), + // Back porch. + Self::make_timing(timings.2 * 5, hsync.disabled(), vsync.disabled(), false), + // Visible portion. + Self::make_timing(timings.3 * 5, hsync.disabled(), vsync.disabled(), false), + ], + } + } + + /// Create a timing buffer for each scan-line in the V-Sync pulse + const fn new_v_pulse( + hsync: SyncPolarity, + vsync: SyncPolarity, + timings: (u32, u32, u32, u32), + ) -> ScanlineTimingBuffer { + ScanlineTimingBuffer { + data: [ + // Front porch (as per the spec) + Self::make_timing(timings.0 * 5, hsync.disabled(), vsync.enabled(), false), + // Sync pulse (as per the spec) + Self::make_timing(timings.1 * 5, hsync.enabled(), vsync.enabled(), false), + // Back porch. + Self::make_timing(timings.2 * 5, hsync.disabled(), vsync.enabled(), false), + // Visible portion. + Self::make_timing(timings.3 * 5, hsync.disabled(), vsync.enabled(), false), + ], + } + } + + /// Generate a 32-bit value we can send to the Timing FIFO. + /// + /// * `period` - The length of this portion of the scan-line, in system clock ticks + /// * `hsync` - true if the H-Sync pin should be high during this period, else false + /// * `vsync` - true if the H-Sync pin should be high during this period, else false + /// * `raise_irq` - true the timing statemachine should raise an IRQ at the start of this period + /// + /// Returns a 32-bit value you can post to the Timing FIFO. + const fn make_timing(period: u32, hsync: bool, vsync: bool, raise_irq: bool) -> u32 { + let command = if raise_irq { + // This command sets IRQ 0. It is the same as: + // + // ``` + // pio::InstructionOperands::IRQ { + // clear: false, + // wait: false, + // index: 0, + // relative: false, + // }.encode() + // ``` + // + // Unfortunately encoding this isn't a const-fn, so we cheat: + 0xc000 + } else { + // This command is a no-op (it moves Y into Y) + // + // ``` + // pio::InstructionOperands::MOV { + // destination: pio::MovDestination::Y, + // op: pio::MovOperation::None, + // source: pio::MovSource::Y, + // }.encode() + // ``` + // + // Unfortunately encoding this isn't a const-fn, so we cheat: + 0xa042 + }; + let mut value: u32 = 0; + if hsync { + value |= 1 << 0; + } + if vsync { + value |= 1 << 1; + } + value |= (period - 6) << 2; + value | command << 16 + } +} + +impl TimingBuffer { + /// Make a timing buffer suitable for 640 x 400 @ 70 Hz + pub const fn make_640x400() -> TimingBuffer { + TimingBuffer { + visible_line: ScanlineTimingBuffer::new_v_visible( + SyncPolarity::Negative, + SyncPolarity::Positive, + (16, 96, 48, 640), + ), + vblank_porch_buffer: ScanlineTimingBuffer::new_v_porch( + SyncPolarity::Negative, + SyncPolarity::Positive, + (16, 96, 48, 640), + ), + vblank_sync_buffer: ScanlineTimingBuffer::new_v_pulse( + SyncPolarity::Negative, + SyncPolarity::Positive, + (16, 96, 48, 640), + ), + visible_lines_ends_at: 399, + front_porch_end_at: 399 + 12, + sync_pulse_ends_at: 399 + 12 + 2, + back_porch_ends_at: 399 + 12 + 2 + 35, + } + } + + /// Make a timing buffer suitable for 640 x 480 @ 60 Hz + pub const fn make_640x480() -> TimingBuffer { + TimingBuffer { + visible_line: ScanlineTimingBuffer::new_v_visible( + SyncPolarity::Negative, + SyncPolarity::Negative, + (16, 96, 48, 640), + ), + vblank_porch_buffer: ScanlineTimingBuffer::new_v_porch( + SyncPolarity::Negative, + SyncPolarity::Negative, + (16, 96, 48, 640), + ), + vblank_sync_buffer: ScanlineTimingBuffer::new_v_pulse( + SyncPolarity::Negative, + SyncPolarity::Negative, + (16, 96, 48, 640), + ), + visible_lines_ends_at: 479, + front_porch_end_at: 479 + 10, + sync_pulse_ends_at: 479 + 10 + 2, + back_porch_ends_at: 479 + 10 + 2 + 33, + } + } +} + +impl RGBColour { + pub const fn from_24bit(red: u8, green: u8, blue: u8) -> RGBColour { + let red: u16 = (red as u16) & 0x00F; + let green: u16 = (green as u16) & 0x00F; + let blue: u16 = (blue as u16) & 0x00F; + RGBColour((blue << 12) | (green << 4) | red) + } +} + +impl RGBPair { + pub const fn from_pixels(first: RGBColour, second: RGBColour) -> RGBPair { + let first: u32 = first.0 as u32; + let second: u32 = second.0 as u32; + RGBPair((second << 16) | first) + } +} + // ----------------------------------------------------------------------------- // End of file // ----------------------------------------------------------------------------- From b1bfe19f9df6cb8cdf516619284ba416325264b2 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Fri, 19 Nov 2021 20:58:18 +0000 Subject: [PATCH 31/41] Implement a basic text console. --- src/main.rs | 40 ++++- src/vga/mod.rs | 415 ++++++++++++++++++++++++++++++++----------------- 2 files changed, 305 insertions(+), 150 deletions(-) diff --git a/src/main.rs b/src/main.rs index 4aa8695..dd8ac0b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -81,10 +81,36 @@ pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER; /// BIOS version const GIT_VERSION: &str = git_version!(); +/// Create a new Text Console +static TEXT_CONSOLE: vga::TextConsole = vga::TextConsole::new(); + // ----------------------------------------------------------------------------- // Functions // ----------------------------------------------------------------------------- +/// Prints to the screen +#[macro_export] +macro_rules! print { + ($($arg:tt)*) => { + { + use core::fmt::Write as _; + write!(&TEXT_CONSOLE, $($arg)*).unwrap(); + } + }; +} + +/// Prints to the screen and puts a new-line on the end +#[macro_export] +macro_rules! println { + () => (print!("\n")); + ($($arg:tt)*) => { + { + use core::fmt::Write as _; + writeln!(&TEXT_CONSOLE, $($arg)*).unwrap(); + } + }; +} + /// This is the entry-point to the BIOS. It is called by cortex-m-rt once the /// `.bss` and `.data` sections have been initialised. #[entry] @@ -96,7 +122,7 @@ fn main() -> ! { // Grab the singleton containing all the RP2040 peripherals let mut pac = pac::Peripherals::take().unwrap(); // Grab the singleton containing all the generic Cortex-M peripherals - let mut core = pac::CorePeripherals::take().unwrap(); + let _core = pac::CorePeripherals::take().unwrap(); // Reset the DMA engine. If we don't do this, starting from probe-run // (as opposed to a cold-start) is unreliable. @@ -168,8 +194,6 @@ fn main() -> ! { let mut b_power_save = pins.b_power_save.into_push_pull_output(); b_power_save.set_high().unwrap(); - info!("Pins OK"); - // Give H-Sync, V-Sync and 12 RGB colour pins to PIO0 to output video let _h_sync = pins.gpio0.into_mode::(); let _v_sync = pins.gpio1.into_mode::(); @@ -186,6 +210,8 @@ fn main() -> ! { let _blue2 = pins.gpio12.into_mode::(); let _blue3 = pins.gpio13.into_mode::(); + info!("Pins OK"); + vga::init( pac.PIO0, pac.DMA, @@ -195,8 +221,14 @@ fn main() -> ! { &mut pac.PSM, ); + TEXT_CONSOLE.set_text_buffer(unsafe { &mut vga::CHAR_ARRAY }); + + info!("VGA intialised"); + + let mut x = 0; loop { - cortex_m::asm::wfi(); + println!("x = {}", x); + x = x + 1; } } diff --git a/src/vga/mod.rs b/src/vga/mod.rs index eab4c36..04e310c 100644 --- a/src/vga/mod.rs +++ b/src/vga/mod.rs @@ -39,7 +39,7 @@ pub(crate) mod font; // Imports // ----------------------------------------------------------------------------- -use core::sync::atomic::{AtomicBool, AtomicU16, Ordering}; +use core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU16, Ordering}; use defmt::*; use pico::hal::pio::PIOExt; @@ -51,7 +51,7 @@ use pico::hal::pio::PIOExt; /// /// This structure is owned entirely by the main thread (or the drawing /// thread). Data handled under interrupt is stored in various other places. -pub struct VideoEngine { +struct RenderEngine { /// How many frames have been drawn frame_count: u32, /// Look-up table mapping two 1-bpp pixels to two 12-bit RGB values (packed into one 32-bit word). @@ -60,6 +60,15 @@ pub struct VideoEngine { lookup: [RGBPair; 4], } +/// Holds some data necessary to present a text console. +/// +/// Used by Core 0 to control writes to a shared text-buffer. +pub struct TextConsole { + current_col: AtomicU16, + current_row: AtomicU16, + text_buffer: AtomicPtr, +} + /// Describes one scan-line's worth of pixels, including the length word required by the Pixel FIFO. #[repr(C, align(16))] struct LineBuffer { @@ -174,25 +183,51 @@ const TIMING_DMA_CHAN: usize = 0; /// DMA channel for the pixel FIFO const PIXEL_DMA_CHAN: usize = 1; -/// 12-bit pixels for the even scan-lines (0, 2, 4 ... NUM_LINES - 2). Defaults to black. +/// One scan-line's worth of 12-bit pixels, used for the even scan-lines (0, 2, 4 ... NUM_LINES-2). +/// +/// Gets read by DMA, which pushes them into the pixel state machine's FIFO. +/// +/// Gets written to by `RenderEngine` running on Core 1. static mut PIXEL_DATA_BUFFER_EVEN: LineBuffer = LineBuffer { length: (MAX_NUM_PIXEL_PAIRS_PER_LINE as u32) - 1, pixels: [RGBPair::from_pixels(colours::WHITE, colours::BLACK); MAX_NUM_PIXEL_PAIRS_PER_LINE], }; -/// 12-bit pixels for the odd scan-lines (1, 3, 5 ... NUM_LINES-1). Defaults to white. +/// One scan-line's worth of 12-bit pixels, used for the odd scan-lines (1, 3, 5 ... NUM_LINES-1). +/// +/// Gets read by DMA, which pushes them into the pixel state machine's FIFO. +/// +/// Gets written to by `RenderEngine` running on Core 1. static mut PIXEL_DATA_BUFFER_ODD: LineBuffer = LineBuffer { length: (MAX_NUM_PIXEL_PAIRS_PER_LINE as u32) - 1, pixels: [RGBPair::from_pixels(colours::BLACK, colours::WHITE); MAX_NUM_PIXEL_PAIRS_PER_LINE], }; -/// Some dummy text to display. +/// This is our text buffer. /// /// This is arranged as `NUM_TEXT_ROWS` rows of `NUM_TEXT_COLS` columns. Each /// item is an index into `font::FONT_DATA` (or a Code-Page 850 character). -static mut CHAR_ARRAY: [u8; NUM_TEXT_COLS as usize * NUM_TEXT_ROWS as usize] = +/// +/// Written to by Core 0, and read from by `RenderEngine` running on Core 1. +pub static mut CHAR_ARRAY: [u8; NUM_TEXT_COLS as usize * NUM_TEXT_ROWS as usize] = [0xB2; NUM_TEXT_COLS as usize * NUM_TEXT_ROWS as usize]; +/// Core 1 entry function. +/// +/// This is a naked function I have pre-compiled to thumb-2 instructions. I +/// could use inline assembler, but then I'd have to make you install +/// arm-none-eabi-as or arm-none-eabi-gcc, or wait until `llvm_asm!` is +/// stablised. +/// +/// We want this function to load the three parameters (which we placed in +/// Core 1's stack) into registers. We then jump to the third of these, which +/// effectively makes a function call with the first two values as function +/// arguments. +static CORE1_ENTRY_FUNCTION: [u16; 2] = [ + 0xbd03, // pop {r0, r1, pc} + 0x46c0, // nop - pad this out to 32-bits long +]; + /// A set of useful constants representing common RGB colours. pub mod colours { /// The colour white @@ -215,107 +250,6 @@ pub mod colours { // Functions // ----------------------------------------------------------------------------- -impl VideoEngine { - // Initialise the main-thread resources - pub fn new() -> VideoEngine { - VideoEngine { - frame_count: 0, - lookup: [ - RGBPair::from_pixels(colours::BLUE, colours::BLUE), - RGBPair::from_pixels(colours::BLUE, colours::WHITE), - RGBPair::from_pixels(colours::WHITE, colours::BLUE), - RGBPair::from_pixels(colours::WHITE, colours::WHITE), - ], - } - } - - pub fn poll(&mut self) { - if DMA_READY.load(Ordering::Relaxed) { - DMA_READY.store(false, Ordering::Relaxed); - unsafe { - CHAR_ARRAY[0] = CHAR_ARRAY[0].wrapping_add(1); - } - let current_line_num = CURRENT_DISPLAY_LINE.load(Ordering::Relaxed); - if current_line_num == 0 { - info!("Frame {}", self.frame_count); - self.frame_count += 1; - } - - // new line - pick a buffer to draw into (not the one that is currently rendering!) - let scan_line_buffer = unsafe { - if (current_line_num & 1) == 0 { - &mut PIXEL_DATA_BUFFER_ODD - } else { - &mut PIXEL_DATA_BUFFER_EVEN - } - }; - - // Convert our position in scan-lines to a text row, and a line within each glyph on that row - let text_row = (current_line_num / font::HEIGHT_PX as u16) as usize; - let font_row = (current_line_num % font::HEIGHT_PX as u16) as usize; - - if text_row < NUM_TEXT_ROWS { - // Note (unsafe): We could stash the char array inside `self` - // but at some point we are going to need one CPU rendering - // the text, and the other CPU running code and writing to - // the buffer. This might be Undefined Behaviour, but - // unfortunately real-time video is all about shared mutable - // state. At least our platform is fixed, so we can simply - // test if it works, for some given version of the Rust compiler. - let row_slice = unsafe { - &CHAR_ARRAY[(text_row * NUM_TEXT_COLS)..((text_row + 1) * NUM_TEXT_COLS)] - }; - // Every font look-up we are about to do for this row will - // involve offsetting by the row within each glyph. As this - // is the same for every glyph on this row, we calculate a - // new pointer once, in advance, and save ourselves an - // addition each time around the loop. - let font_ptr = unsafe { font::DATA.as_ptr().add(font_row) }; - - // Get a pointer into our scan-line buffer - let scan_line_buffer_ptr = scan_line_buffer.pixels.as_mut_ptr(); - let mut px_idx = 0; - - // Convert from characters to coloured pixels, using the font as a look-up table. - for ch in row_slice.iter() { - let index = (*ch as isize) * 16; - // Note (unsafe): We use pointer arithmetic here because we - // can't afford a bounds-check on an array. This is safe - // because the font is `256 * width` bytes long and we can't - // index more than `255 * width` bytes into it. - let mono_pixels = unsafe { *font_ptr.offset(index) } as usize; - // Convert from eight mono pixels in one byte to four RGB pairs - unsafe { - core::ptr::write_volatile( - scan_line_buffer_ptr.offset(px_idx), - self.lookup[(mono_pixels >> 6) & 3], - ); - core::ptr::write_volatile( - scan_line_buffer_ptr.offset(px_idx + 1), - self.lookup[(mono_pixels >> 4) & 3], - ); - core::ptr::write_volatile( - scan_line_buffer_ptr.offset(px_idx + 2), - self.lookup[(mono_pixels >> 2) & 3], - ); - core::ptr::write_volatile( - scan_line_buffer_ptr.offset(px_idx + 3), - self.lookup[mono_pixels & 3], - ); - } - px_idx += 4; - } - } - } - } -} - -impl Default for VideoEngine { - fn default() -> Self { - VideoEngine::new() - } -} - /// Initialise all the static data and peripherals we need for our video display. /// /// We need to keep `pio` and `dma` to run the video. We need `resets` to set @@ -511,7 +445,7 @@ pub fn init( dma.multi_chan_trigger .write(|w| unsafe { w.bits((1 << PIXEL_DMA_CHAN) | (1 << TIMING_DMA_CHAN)) }); - info!("DMA enabled"); + debug!("DMA enabled"); unsafe { // Hand off the DMA peripheral to the interrupt @@ -523,14 +457,14 @@ pub fn init( pico::hal::pac::NVIC::unmask(pico::hal::pac::Interrupt::DMA_IRQ_0); } - info!("IRQs enabled"); + debug!("IRQs enabled"); - info!("DMA set-up complete"); + debug!("DMA set-up complete"); timing_sm.start(); pixel_sm.start(); - info!("State Machines running"); + debug!("State Machines running"); // We drop our state-machine and PIO objects here - this means the video // cannot be reconfigured at a later time, but they do keep on running @@ -542,28 +476,15 @@ pub fn init( multicore_launch_core1_with_stack(core1_main, &mut CORE1_STACK, ppb, fifo, psm); } - info!("Core 1 running"); + debug!("Core 1 running"); } /// The bootrom code will call this function on core1 to perform any set-up, before the /// entry function is called. extern "C" fn core1_wrapper(entry_func: extern "C" fn() -> u32, _stack_base: *mut u32) -> u32 { - return entry_func(); + entry_func() } -/// Core 1 entry function. -/// -/// This is a naked function I have pre-compiled to thumb-2 instructions. I -/// could use inline assembler, but then I'd have to make you install -/// arm-none-eabi-as or arm-none-eabi-gcc, or wait until `llvm_asm!` is -/// stablised. -static CORE1_ENTRY_FUNCTION: [u16; 2] = [ - // pop {r0, r1, pc} - load the three parameters (which we placed in Core 1's stack) into registers. - // Loading PC is basically a jump. - 0xbd03, // nop - pad this out to 32-bits long - 0x46c0, -]; - /// Starts core 1 running the given function, with the given stack. fn multicore_launch_core1_with_stack( main_func: unsafe extern "C" fn() -> u32, @@ -572,7 +493,7 @@ fn multicore_launch_core1_with_stack( fifo: &mut pico::hal::sio::SioFifo, psm: &mut pico::hal::pac::PSM, ) { - info!("Resetting CPU1..."); + debug!("Resetting CPU1..."); psm.frce_off.modify(|_, w| w.proc1().set_bit()); while !psm.frce_off.read().proc1().bit_is_set() { @@ -580,24 +501,27 @@ fn multicore_launch_core1_with_stack( } psm.frce_off.modify(|_, w| w.proc1().clear_bit()); - info!("Setting up stack..."); + debug!("Setting up stack..."); - // Gets popped into r0 by CORE1_ENTRY_FUNCTION. This is the function we - // want to run. It appears in `core1_wrapper` as the first argument. + // Gets popped into `r0` by CORE1_ENTRY_FUNCTION. This is the `main` + // function we want to run. It appears in the call to `core1_wrapper` as + // the first argument. stack[stack.len() - 3] = main_func as *const () as usize; - // Gets popped into r1 by CORE1_ENTRY_FUNCTION. This is the top of stack - // for Core 1. It appears in `core1_wrapper` as the second argument. + // Gets popped into `r1` by CORE1_ENTRY_FUNCTION. This is the top of stack + // for Core 1. It appears in the call to `core1_wrapper` as the second + // argument. stack[stack.len() - 2] = stack.as_ptr() as *const _ as usize; - // Gets popped into pc by CORE1_ENTRY_FUNCTION. This is the function + // Gets popped into `pc` by CORE1_ENTRY_FUNCTION. This is the function // `CORE1_ENTRY_FUNCTION` will jump to, passing the above two values as // arguments. stack[stack.len() - 1] = core1_wrapper as *const () as usize; - // Point into the top of the stack (so there are three values pushed onto it, i.e. at/above it) + // Point into the top of the stack (so there are three values pushed onto + // it, i.e. at/above it) let stack_ptr = unsafe { stack.as_mut_ptr().add(stack.len() - 3) }; - info!("Stack ptr is 0x{:x}", stack_ptr); - info!("Stack bottom is 0x{:x}", stack.as_ptr()); - info!("Stack top is 0x{:x}", &stack[stack.len() - 4..stack.len()]); + debug!("Stack ptr is 0x{:x}", stack_ptr); + debug!("Stack bottom is 0x{:x}", stack.as_ptr()); + debug!("Stack top is 0x{:x}", &stack[stack.len() - 4..stack.len()]); // This is the launch sequence we send to core1, to get it to leave the // boot ROM and run our code. @@ -616,29 +540,29 @@ fn multicore_launch_core1_with_stack( 'outer: loop { for cmd in cmd_sequence.iter() { - info!("Sending command {:x}...", *cmd); + debug!("Sending command {:x}...", *cmd); // we drain before sending a 0 if *cmd == 0 { - info!("Draining FIFO..."); + debug!("Draining FIFO..."); fifo.drain(); // core 1 may be waiting for fifo space cortex_m::asm::sev(); } - info!("Pushing to FIFO..."); + debug!("Pushing to FIFO..."); fifo.write_blocking(*cmd); - info!("Getting response from FIFO..."); + debug!("Getting response from FIFO..."); let response = loop { if let Some(x) = fifo.read() { break x; } else { - info!("ST is {:x}", fifo.status()); + debug!("ST is {:x}", fifo.status()); } }; // move to next state on correct response otherwise start over - info!("Got {:x}", response); + debug!("Got {:x}", response); if *cmd != response { continue 'outer; } @@ -650,11 +574,11 @@ fn multicore_launch_core1_with_stack( unsafe { pico::hal::pac::NVIC::unmask(pico::hal::pac::Interrupt::SIO_IRQ_PROC0) }; } - info!("Waiting for Core 1 to start..."); + debug!("Waiting for Core 1 to start..."); while !CORE1_START_FLAG.load(Ordering::Relaxed) { cortex_m::asm::nop(); } - info!("Core 1 started!!"); + debug!("Core 1 started!!"); } /// This function runs the video processing loop on Core 1. @@ -668,7 +592,7 @@ fn multicore_launch_core1_with_stack( unsafe extern "C" fn core1_main() -> u32 { CORE1_START_FLAG.store(true, Ordering::Relaxed); - let mut video = VideoEngine::new(); + let mut video = RenderEngine::new(); loop { // This function currently consumes about 70% CPU (or rather, 90% CPU @@ -763,6 +687,205 @@ pub unsafe fn irq() { } } +impl RenderEngine { + // Initialise the main-thread resources + pub fn new() -> RenderEngine { + RenderEngine { + frame_count: 0, + lookup: [ + RGBPair::from_pixels(colours::BLUE, colours::BLUE), + RGBPair::from_pixels(colours::BLUE, colours::WHITE), + RGBPair::from_pixels(colours::WHITE, colours::BLUE), + RGBPair::from_pixels(colours::WHITE, colours::WHITE), + ], + } + } + + pub fn poll(&mut self) { + if DMA_READY.load(Ordering::Relaxed) { + DMA_READY.store(false, Ordering::Relaxed); + let current_line_num = CURRENT_DISPLAY_LINE.load(Ordering::Relaxed); + if current_line_num == 0 { + debug!("Frame {}", self.frame_count); + self.frame_count += 1; + } + + // new line - pick a buffer to draw into (not the one that is currently rendering!) + let scan_line_buffer = unsafe { + if (current_line_num & 1) == 0 { + &mut PIXEL_DATA_BUFFER_ODD + } else { + &mut PIXEL_DATA_BUFFER_EVEN + } + }; + + // Convert our position in scan-lines to a text row, and a line within each glyph on that row + let text_row = (current_line_num / font::HEIGHT_PX as u16) as usize; + let font_row = (current_line_num % font::HEIGHT_PX as u16) as usize; + + if text_row < NUM_TEXT_ROWS { + // Note (unsafe): We could stash the char array inside `self` + // but at some point we are going to need one CPU rendering + // the text, and the other CPU running code and writing to + // the buffer. This might be Undefined Behaviour, but + // unfortunately real-time video is all about shared mutable + // state. At least our platform is fixed, so we can simply + // test if it works, for some given version of the Rust compiler. + let row_slice = unsafe { + &CHAR_ARRAY[(text_row * NUM_TEXT_COLS)..((text_row + 1) * NUM_TEXT_COLS)] + }; + // Every font look-up we are about to do for this row will + // involve offsetting by the row within each glyph. As this + // is the same for every glyph on this row, we calculate a + // new pointer once, in advance, and save ourselves an + // addition each time around the loop. + let font_ptr = unsafe { font::DATA.as_ptr().add(font_row) }; + + // Get a pointer into our scan-line buffer + let scan_line_buffer_ptr = scan_line_buffer.pixels.as_mut_ptr(); + let mut px_idx = 0; + + // Convert from characters to coloured pixels, using the font as a look-up table. + for ch in row_slice.iter() { + let index = (*ch as isize) * 16; + // Note (unsafe): We use pointer arithmetic here because we + // can't afford a bounds-check on an array. This is safe + // because the font is `256 * width` bytes long and we can't + // index more than `255 * width` bytes into it. + let mono_pixels = unsafe { *font_ptr.offset(index) } as usize; + // Convert from eight mono pixels in one byte to four RGB pairs + unsafe { + core::ptr::write_volatile( + scan_line_buffer_ptr.offset(px_idx), + self.lookup[(mono_pixels >> 6) & 3], + ); + core::ptr::write_volatile( + scan_line_buffer_ptr.offset(px_idx + 1), + self.lookup[(mono_pixels >> 4) & 3], + ); + core::ptr::write_volatile( + scan_line_buffer_ptr.offset(px_idx + 2), + self.lookup[(mono_pixels >> 2) & 3], + ); + core::ptr::write_volatile( + scan_line_buffer_ptr.offset(px_idx + 3), + self.lookup[mono_pixels & 3], + ); + } + px_idx += 4; + } + } + } + } +} + +impl Default for RenderEngine { + fn default() -> Self { + RenderEngine::new() + } +} + +impl TextConsole { + /// Create a TextConsole. + /// + /// Has no buffer associated with it + pub const fn new() -> TextConsole { + TextConsole { + current_row: AtomicU16::new(0), + current_col: AtomicU16::new(0), + text_buffer: AtomicPtr::new(core::ptr::null_mut()), + } + } + + /// Update the text buffer we are using. + /// + /// Will reset the cursor. The screen is not cleared. + pub fn set_text_buffer(&self, text_buffer: &'static mut [u8; NUM_TEXT_ROWS * NUM_TEXT_COLS]) { + self.text_buffer + .store(text_buffer.as_mut_ptr(), Ordering::Relaxed) + } + + /// Place a single Code Page 850 encoded 8-bit character on the screen. + /// + /// Adjusts the current row and column automatically. Also understands + /// Carriage Return and New Line bytes. + pub fn write_cp850_char(&self, cp850_char: u8) { + // Load from global state + let mut row = self.current_row.load(Ordering::Relaxed); + let mut col = self.current_col.load(Ordering::Relaxed); + let buffer = self.text_buffer.load(Ordering::Relaxed); + + if !buffer.is_null() { + self.write_at(cp850_char, buffer, &mut row, &mut col); + // Push back to global state + self.current_row.store(row as u16, Ordering::Relaxed); + self.current_col.store(col as u16, Ordering::Relaxed); + } + } + + fn write_at(&self, cp850_char: u8, buffer: *mut u8, row: &mut u16, col: &mut u16) { + if cp850_char == b'\r' { + *col = 0; + } else if cp850_char == b'\n' { + *col = 0; + *row += 1; + } else { + let offset = (*col as usize) + (NUM_TEXT_COLS * (*row as usize)); + // Note (safety): This is safe as we bound `col` and `row` + unsafe { buffer.add(offset).write_volatile(cp850_char) }; + *col += 1; + } + if *col == (NUM_TEXT_COLS as u16) { + *col = 0; + *row += 1; + } + if *row == (NUM_TEXT_ROWS as u16) { + // Stay on last line + *row = (NUM_TEXT_ROWS - 1) as u16; + + unsafe { + core::ptr::copy( + buffer.add(NUM_TEXT_COLS as usize), + buffer, + NUM_TEXT_COLS * (NUM_TEXT_ROWS - 1), + ) + }; + + for blank_col in 0..NUM_TEXT_COLS { + let offset = (blank_col as usize) + (NUM_TEXT_COLS * (*row as usize)); + unsafe { buffer.add(offset).write_volatile(b' ') }; + } + } + } +} + +unsafe impl Sync for TextConsole {} + +impl core::fmt::Write for &TextConsole { + /// Allows us to call `writeln!(some_text_console, "hello")` + fn write_str(&mut self, s: &str) -> core::fmt::Result { + // Load from global state + let mut row = self.current_row.load(Ordering::Relaxed); + let mut col = self.current_col.load(Ordering::Relaxed); + let buffer = self.text_buffer.load(Ordering::Relaxed); + + if !buffer.is_null() { + // Convert a String (a collection of bytes which are valid UTF-8) to CP850 (very badly) + for ch in s.chars() { + let b = if (ch as u32) < 127 { ch as u8 } else { b'?' }; + + self.write_at(b, buffer, &mut row, &mut col); + } + + // Push back to global state + self.current_row.store(row as u16, Ordering::Relaxed); + self.current_col.store(col as u16, Ordering::Relaxed); + } + + Ok(()) + } +} + impl LineBuffer { /// Convert the line buffer to a 32-bit address that the DMA engine understands. fn as_ptr(&self) -> u32 { From 40e3e2ef31073d9f0914093a0eb10e4bd0cac581 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Fri, 19 Nov 2021 20:59:38 +0000 Subject: [PATCH 32/41] Let's not crash after 2**32 loops. --- src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index dd8ac0b..a4e5f9a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -225,10 +225,10 @@ fn main() -> ! { info!("VGA intialised"); - let mut x = 0; + let mut x: u32 = 0; loop { println!("x = {}", x); - x = x + 1; + x = x.wrapping_add(1); } } From 8dbaea7c30c63e4cdaa77533c71e51744390587f Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Thu, 25 Nov 2021 19:00:06 +0000 Subject: [PATCH 33/41] Use published deps. We'll change from the add_sio_fifo branch once the PR is merged. --- Cargo.toml | 12 ++++-------- src/main.rs | 2 +- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index fda4ab1..55fdb91 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,8 +11,7 @@ version = "0.2.0" # Useful Cortex-M specific functions (e.g. SysTick) cortex-m = "0.7" # The Raspberry Pi Pico HAL -pico = { git = "https://github.com/rp-rs/rp-hal.git" } -# pico = { path = "/home/jonathan/Documents/rp-rs/rp-hal/boards/pico" } +pico = { git = "https://github.com/rp-rs/rp-hal.git", branch="add_sio_fifo" } # Cortex-M run-time (or start-up) code cortex-m-rt = "0.7" # The BIOS API we export to the OS @@ -20,13 +19,13 @@ neotron-common-bios = { git = "https://github.com/Neotron-Compute/Neotron-Common # For time keeping/handling embedded-time = "0.12" # For the RP2040 bootloader -rp2040-boot2 = "0.1" +rp2040-boot2 = "0.2" # For hardware abstraction traits embedded-hal ="0.2" # Gives us formatted PC-side logging -defmt = "0.2" +defmt = "0.3" # Sends defmt logs to the SWD debugger -defmt-rtt = "0.2" +defmt-rtt = "0.3" # Send panics to the debugger panic-probe = "0.2" # Fetches the BIOS version from git @@ -64,6 +63,3 @@ codegen-units = 1 debug = true # better optimizations lto = true - -[patch.crates-io] -rp2040-pac = { path = "/home/jonathan/Documents/rp-rs/rp2040-pac" } diff --git a/src/main.rs b/src/main.rs index a4e5f9a..e9d44e4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -76,7 +76,7 @@ use pico::{ /// See `memory.x` for a definition of the `.boot2` section. #[link_section = ".boot2"] #[used] -pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER; +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_W25Q080; /// BIOS version const GIT_VERSION: &str = git_version!(); From 537a8c9bc6d3240c026eab8d9f621812b5a277fe Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Thu, 25 Nov 2021 19:04:40 +0000 Subject: [PATCH 34/41] Add notes about `cargo run` options. --- .cargo/config.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 68eeda7..919b29e 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,6 +1,8 @@ [target.thumbv6m-none-eabi] +# This will make a UF2 and copy it to the RP2040's Mass Storage Device bootloader # runner = "elf2uf2-rs -d" -runner = "probe-run-rp --chip RP2040" +# This will flash over SWD with any compatible probe it finds. You need 0.3.1 or higher for RP2040 support. +runner = "probe-run --chip RP2040" rustflags = [ # This is needed if your flash or ram addresses are not aligned to 0x10000 in memory.x From 7b4df58938765818f37190b266f56f87340cfe02 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Thu, 25 Nov 2021 19:06:05 +0000 Subject: [PATCH 35/41] Switch to main branch for pico BSP. SIO branch was merged. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 55fdb91..adc2f53 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ version = "0.2.0" # Useful Cortex-M specific functions (e.g. SysTick) cortex-m = "0.7" # The Raspberry Pi Pico HAL -pico = { git = "https://github.com/rp-rs/rp-hal.git", branch="add_sio_fifo" } +pico = { git = "https://github.com/rp-rs/rp-hal.git" } # Cortex-M run-time (or start-up) code cortex-m-rt = "0.7" # The BIOS API we export to the OS From e4ef0f5ba364e611359d8b8a3c77f673fe59438f Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Thu, 25 Nov 2021 19:43:00 +0000 Subject: [PATCH 36/41] Clean up github workflow. - Runs clippy - Checks it builds on multiple OSes - Checks stable and nightly - Doesn't install GCC --- .github/workflows/rust.yml | 52 +++++++++++++++++++++++++++----------- 1 file changed, 37 insertions(+), 15 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index cfd44f7..2bf88ba 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -2,21 +2,43 @@ name: Build on: [push, pull_request] +env: + CARGO_TERM_COLOR: always + jobs: build: - - runs-on: ubuntu-latest - + continue-on-error: ${{ matrix.experimental || false }} + strategy: + matrix: + # All generated code should be running on stable now + rust: [nightly, stable] + include: + # Nightly is only for reference and allowed to fail + - rust: nightly + experimental: true + os: + - ubuntu-latest + - macOS-latest + - windows-latest + runs-on: ${{ matrix.os }} steps: - - name: Checkout - uses: actions/checkout@v1 - with: - submodules: true - - name: Install GCC ARM - run: sudo apt-get update && sudo apt-get install -y gcc-arm-none-eabi - - name: Add Target - run: rustup target add thumbv6m-none-eabi - - name: Build - run: cargo build --verbose - - name: Build Release - run: cargo build --release --verbose + - name: Checkout + uses: actions/checkout@v2 + with: + submodules: true + - name: Build + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.rust }} + override: true + - name: Install Rust targets for ${{ matrix.rust }} + run: rustup target install --toolchain=${{ matrix.rust }} thumbv6m-none-eabi + - name: Build + run: cargo build --verbose + - name: Build Release + run: cargo build --release --verbose + - name: Clippy + uses: actions-rs/clippy-check@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} From 80a3e4887fa20f3b5c133a4ea308693f8468b4b3 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Sun, 13 Mar 2022 22:01:01 +0000 Subject: [PATCH 37/41] Re-arranged memory. BIOS is at the top of the 256 KB block of RAM. Application is at the bottom. Core 0 and Core 1 stacks get the upper two 4KB blocks. --- Cargo.toml | 12 +- README.md | 10 +- memory.x | 39 ++++-- src/main.rs | 80 ++++++++++--- src/vga/mod.rs | 313 +++++++++++++++++++++++++++++++++++++------------ 5 files changed, 342 insertions(+), 112 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index adc2f53..4d21688 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ version = "0.2.0" # Useful Cortex-M specific functions (e.g. SysTick) cortex-m = "0.7" # The Raspberry Pi Pico HAL -pico = { git = "https://github.com/rp-rs/rp-hal.git" } +rp-pico = "0.3" # Cortex-M run-time (or start-up) code cortex-m-rt = "0.7" # The BIOS API we export to the OS @@ -28,12 +28,12 @@ defmt = "0.3" defmt-rtt = "0.3" # Send panics to the debugger panic-probe = "0.2" -# Fetches the BIOS version from git +# Fetches git hashes from git at compile time git-version = "0.3" -# RP2040 PIO driver -pio = { git = "https://github.com/rp-rs/pio-rs.git", branch = "main" } -# RP2040 PIO driver macros -pio-proc = { git = "https://github.com/rp-rs/pio-rs.git", branch = "main" } +# RP2040 PIO assembler +pio = "0.2" +# Macros for RP2040 PIO assembler +pio-proc = "0.2" [features] default = [ diff --git a/README.md b/README.md index 7eb5ca7..c5e9444 100644 --- a/README.md +++ b/README.md @@ -47,18 +47,18 @@ The Neotron BIOS uses the [defmt](https://crates.io/crates/defmt) crate to provi 2. Flash your *Debugger* Pico with https://github.com/majbthrd/DapperMime firmware (e.g. by copying the UF2 file to the USB Mass Storage device) -3. On your PC, install *probe-rs-rp* from the RP2040-specific [probe-run](https://github.com/knurling-rs/probe-run) fork at https://github.com/rp-rs/probe-run. +3. On your PC, install [*probe-rs*](https://github.com/knurling-rs/probe-run), the programming tool from [Ferrous System's](https://www.ferrous-systems.com) [Knurling Project](https://github.com/knurling). ```console -user@host ~ $ cargo install probe-rs-rp +user@host ~ $ cargo install probe-rs ``` 4. Power on your Neotron Pico. -5. Build and load the Neotron BIOS, and view the debug output stream, with `cargo run`: +5. Build and load the Neotron BIOS, and view the debug output stream, with `cargo run --release`: ```console -user@host ~/neotron-pico-bios $ cargo run --release +user@host ~/neotron-pico-bios $ DEFMT_LOG=debug cargo run --release Compiling neotron-pico-bios v0.1.0 (/home/jonathan/Documents/neotron/neotron-pico-bios) Finished release [optimized + debuginfo] target(s) in 0.76s Running `probe-run-rp --chip RP2040 target/thumbv6m-none-eabi/release/neotron-pico-bios` @@ -75,6 +75,8 @@ user@host ~/neotron-pico-bios $ cargo run --release └─ neotron_pico_bios::__cortex_m_rt_main @ src/main.rs:128 ``` +You should see your Neotron Pico boot, both over RTT in the `probe-run` output, and also on the VGA output. + ## Changelog See [CHANGELOG.md](./CHANGELOG.md) diff --git a/memory.x b/memory.x index 6453784..6d55748 100644 --- a/memory.x +++ b/memory.x @@ -21,23 +21,46 @@ MEMORY { /* * This is the remainder of the 2048 KiB flash chip. */ - FLASH_OS: ORIGIN = 0x10020000, LENGTH = 2048K - 128K + FLASH_OS : ORIGIN = 0x10020000, LENGTH = 2048K - 128K /* - * This is the internal SRAM in the RP2040. + * This is the bottom of the four striped banks of SRAM in the RP2040. */ - RAM : ORIGIN = 0x20000000, LENGTH = 256K + RAM_OS : ORIGIN = 0x20000000, LENGTH = 0x3C000 + /* + * This is the top of the four striped banks of SRAM in the RP2040. + */ + RAM : ORIGIN = 0x2003C000, LENGTH = 16K + /* + * This is the fifth bank, a 4KB block. We use this for Core 0 Stack. + */ + RAM_CORE0_STACK : ORIGIN = 0x20040000, LENGTH = 4K + /* + * This is the sixth bank, a 4KB block. We use this for Core 1 Stack. + */ + RAM_CORE1_STACK : ORIGIN = 0x20041000, LENGTH = 4K } -/* This is where the call stack will be allocated. */ -/* The stack is of the full descending type. */ -/* You may want to use this variable to locate the call stack and static - variables in different memory regions. Below is shown the default value */ -_stack_start = ORIGIN(RAM) + LENGTH(RAM); + +/* + * This is where the call stack for Core 0 will be located. The stack is of + * the full descending type. You may want to use this variable to locate the + * call stack and static variables in different memory regions. Below is + * shown the default value + */ +_stack_start = ORIGIN(RAM_CORE0_STACK) + LENGTH(RAM_CORE0_STACK); + +/* + * This is where the call stack for Core 1 will be located. + */ +_core1_stack_bottom = ORIGIN(RAM_CORE1_STACK); +_core1_stack_len = LENGTH(RAM_CORE1_STACK); /* * Export some symbols to tell the BIOS where it might find the OS. */ _flash_os_start = ORIGIN(FLASH_OS); _flash_os_len = LENGTH(FLASH_OS); +_ram_os_start = ORIGIN(RAM_OS); +_ram_os_len = LENGTH(RAM_OS); SECTIONS { /* ### RP2040 Boot loader */ diff --git a/src/main.rs b/src/main.rs index e9d44e4..824c19a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -44,13 +44,13 @@ pub mod vga; // ----------------------------------------------------------------------------- use cortex_m_rt::entry; -use defmt::*; +use defmt::info; use defmt_rtt as _; use embedded_hal::digital::v2::OutputPin; use embedded_time::rate::*; use git_version::git_version; use panic_probe as _; -use pico::{ +use rp_pico::{ self, hal::{ self, @@ -68,16 +68,6 @@ use pico::{ // Static and Const Data // ----------------------------------------------------------------------------- -/// This is the standard RP2040 bootloader. It must be stored in the first 256 -/// bytes of the external SPI Flash chip. It will map the external SPI flash -/// chip to address `0x1000_0000` and jump to an Interrupt Vector Table at -/// address `0x1000_0100` (i.e. immediately after the bootloader). -/// -/// See `memory.x` for a definition of the `.boot2` section. -#[link_section = ".boot2"] -#[used] -pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_W25Q080; - /// BIOS version const GIT_VERSION: &str = git_version!(); @@ -88,7 +78,10 @@ static TEXT_CONSOLE: vga::TextConsole = vga::TextConsole::new(); // Functions // ----------------------------------------------------------------------------- -/// Prints to the screen +/// Prints to the screen. +/// +/// This function is NOT interrupt safe. Only call from the main thread, on +/// core 1. #[macro_export] macro_rules! print { ($($arg:tt)*) => { @@ -99,7 +92,10 @@ macro_rules! print { }; } -/// Prints to the screen and puts a new-line on the end +/// Prints to the screen and puts a new-line on the end. +/// +/// This function is NOT interrupt safe. Only call from the main thread, on +/// core 1. #[macro_export] macro_rules! println { () => (print!("\n")); @@ -136,12 +132,12 @@ fn main() -> ! { // Run at 126 MHz SYS_PLL, 48 MHz, USB_PLL - let xosc = hal::xosc::setup_xosc_blocking(pac.XOSC, pico::XOSC_CRYSTAL_FREQ.Hz()) + let xosc = hal::xosc::setup_xosc_blocking(pac.XOSC, rp_pico::XOSC_CRYSTAL_FREQ.Hz()) .map_err(|_x| false) .unwrap(); // Configure watchdog tick generation to tick over every microsecond - watchdog.enable_tick_generation((pico::XOSC_CRYSTAL_FREQ / 1_000_000) as u8); + watchdog.enable_tick_generation((rp_pico::XOSC_CRYSTAL_FREQ / 1_000_000) as u8); let mut clocks = hal::clocks::ClocksManager::new(pac.CLOCKS); @@ -183,7 +179,7 @@ fn main() -> ! { let mut sio = hal::sio::Sio::new(pac.SIO); // Configure and grab all the RP2040 pins the Pico exposes. - let pins = pico::Pins::new( + let pins = rp_pico::Pins::new( pac.IO_BANK0, pac.PADS_BANK0, sio.gpio_bank0, @@ -223,11 +219,57 @@ fn main() -> ! { TEXT_CONSOLE.set_text_buffer(unsafe { &mut vga::CHAR_ARRAY }); - info!("VGA intialised"); + for _col in 0..vga::NUM_TEXT_ROWS { + println!(); + } + + TEXT_CONSOLE.move_to(0, 0); + + println!("Neotron Pico BIOS, for the Raspberry Pi RP2040"); + println!("Copyright © Jonathan 'theJPster' Pallant and the Neotron Developers, 2021"); + println!("Version {}", GIT_VERSION); + println!(); + println!("This program is free software: you can redistribute it and/or modify"); + println!("it under the terms of the GNU General Public License as published by"); + println!("the Free Software Foundation, either version 3 of the License, or"); + println!("(at your option) any later version."); + println!(); + println!("This program is distributed in the hope that it will be useful,"); + println!("but WITHOUT ANY WARRANTY; without even the implied warranty of"); + println!("MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the"); + println!("GNU General Public License for more details."); + println!(); + println!("You should have received a copy of the GNU General Public License"); + println!("along with this program. If not, see https://www.gnu.org/licenses/."); + println!(); + println!("Searching for Neotron OS..."); + + extern "C" { + static mut _flash_os_start: u32; + static mut _flash_os_len: u32; + static mut _ram_os_start: u32; + static mut _ram_os_len: u32; + } + + let flash_os_start = unsafe { + &mut _flash_os_start as *mut u32 as usize + }; + let flash_os_len = unsafe { + &mut _flash_os_len as *mut u32 as usize + }; + let ram_os_start = unsafe { + &mut _ram_os_start as *mut u32 as usize + }; + let ram_os_len = unsafe { + &mut _ram_os_len as *mut u32 as usize + }; + + println!("OS Flash is {:08x}, {} bytes", flash_os_start, flash_os_len); + println!("OS RAM is {:08x}, {} bytes", ram_os_start, ram_os_len); let mut x: u32 = 0; loop { - println!("x = {}", x); + print!("\rx = {}", x); x = x.wrapping_add(1); } } diff --git a/src/vga/mod.rs b/src/vga/mod.rs index 04e310c..e723d86 100644 --- a/src/vga/mod.rs +++ b/src/vga/mod.rs @@ -40,8 +40,8 @@ pub(crate) mod font; // ----------------------------------------------------------------------------- use core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU16, Ordering}; -use defmt::*; -use pico::hal::pio::PIOExt; +use defmt::{debug, trace}; +use rp_pico::hal::pio::PIOExt; // ----------------------------------------------------------------------------- // Types @@ -210,7 +210,7 @@ static mut PIXEL_DATA_BUFFER_ODD: LineBuffer = LineBuffer { /// /// Written to by Core 0, and read from by `RenderEngine` running on Core 1. pub static mut CHAR_ARRAY: [u8; NUM_TEXT_COLS as usize * NUM_TEXT_ROWS as usize] = - [0xB2; NUM_TEXT_COLS as usize * NUM_TEXT_ROWS as usize]; + [0u8; NUM_TEXT_COLS as usize * NUM_TEXT_ROWS as usize]; /// Core 1 entry function. /// @@ -258,9 +258,9 @@ pub fn init( pio: super::pac::PIO0, dma: super::pac::DMA, resets: &mut super::pac::RESETS, - ppb: &mut pico::hal::pac::PPB, - fifo: &mut pico::hal::sio::SioFifo, - psm: &mut pico::hal::pac::PSM, + ppb: &mut crate::pac::PPB, + fifo: &mut rp_pico::hal::sio::SioFifo, + psm: &mut crate::pac::PSM, ) { // Grab PIO0 and the state machines it contains let (mut pio, sm0, sm1, _sm2, _sm3) = pio.split(resets); @@ -279,21 +279,18 @@ pub fn init( // of clock cycles. // // Note: autopull should be set to 32-bits, OSR is set to shift right. - let timing_program = pio_proc::pio!( - 32, - " - .wrap_target - ; Step 1. Push next 2 bits of OSR into `pins`, to set H-Sync and V-Sync - out pins, 2 - ; Step 2. Push last 14 bits of OSR into X for the timing loop. - out x, 14 - ; Step 3. Execute bottom 16-bits of OSR as an instruction. This take two cycles. - out exec, 16 - loop0: - ; Spin until X is zero - jmp x-- loop0 - .wrap - " + let timing_program = pio_proc::pio_asm!( + ".wrap_target" + // Step 1. Push next 2 bits of OSR into `pins`, to set H-Sync and V-Sync + "out pins, 2" + // Step 2. Push last 14 bits of OSR into X for the timing loop. + "out x, 14" + // Step 3. Execute bottom 16-bits of OSR as an instruction. This take two cycles. + "out exec, 16" + // Spin until X is zero + "loop0:" + "jmp x-- loop0" + ".wrap" ); // This is the video pixels program. It waits for an IRQ @@ -308,25 +305,22 @@ pub fn init( // we read the length from the FIFO as well, all hell breaks loose. // // Note autopull should be set to 32-bits, OSR is set to shift right. - let pixel_program = pio_proc::pio!( - 32, - " - .wrap_target - ; Wait for timing state machine to start visible line - wait 1 irq 0 - ; Read the line length (in pixel-pairs) - out x, 32 - loop1: - ; Write out first pixel - takes 5 clocks per pixel - out pins, 16 [4] - ; Write out second pixel - takes 5 clocks per pixel (allowing one clock for the jump) - out pins, 16 [3] - ; Repeat until all pixel pairs sent - jmp x-- loop1 - ; Clear all pins after visible section - mov pins null - .wrap - " + let pixel_program = pio_proc::pio_asm!( + ".wrap_target" + // Wait for timing state machine to start visible line + "wait 1 irq 0" + // Read the line length (in pixel-pairs) + "out x, 32" + "loop1:" + // Write out first pixel - takes 5 clocks per pixel + "out pins, 16 [4]" + // Write out second pixel - takes 5 clocks per pixel (allowing one clock for the jump) + "out pins, 16 [3]" + // Repeat until all pixel pairs sent + "jmp x-- loop1" + // Clear all pins after visible section + "mov pins null" + ".wrap" ); // These two state machines run thus: @@ -356,16 +350,16 @@ pub fn init( let timing_installed = pio.install(&timing_program.program).unwrap(); let (mut timing_sm, _, timing_fifo) = - pico::hal::pio::PIOBuilder::from_program(timing_installed) - .buffers(pico::hal::pio::Buffers::OnlyTx) + rp_pico::hal::pio::PIOBuilder::from_program(timing_installed) + .buffers(rp_pico::hal::pio::Buffers::OnlyTx) .out_pins(0, 2) // H-Sync is GPIO0, V-Sync is GPIO1 .autopull(true) - .out_shift_direction(pico::hal::pio::ShiftDirection::Right) + .out_shift_direction(rp_pico::hal::pio::ShiftDirection::Right) .pull_threshold(32) .build(sm0); timing_sm.set_pindirs([ - (0, pico::hal::pio::PinDir::Output), - (1, pico::hal::pio::PinDir::Output), + (0, rp_pico::hal::pio::PinDir::Output), + (1, rp_pico::hal::pio::PinDir::Output), ]); // Important notes! @@ -376,14 +370,15 @@ pub fn init( // each line differs by some number of 126 MHz clock cycles). let pixels_installed = pio.install(&pixel_program.program).unwrap(); - let (mut pixel_sm, _, pixel_fifo) = pico::hal::pio::PIOBuilder::from_program(pixels_installed) - .buffers(pico::hal::pio::Buffers::OnlyTx) - .out_pins(2, 12) // Red0 is GPIO2, Blue3 is GPIO13 - .autopull(true) - .out_shift_direction(pico::hal::pio::ShiftDirection::Right) - .pull_threshold(32) // We read all 32-bits in each FIFO word - .build(sm1); - pixel_sm.set_pindirs((2..=13).map(|x| (x, pico::hal::pio::PinDir::Output))); + let (mut pixel_sm, _, pixel_fifo) = + rp_pico::hal::pio::PIOBuilder::from_program(pixels_installed) + .buffers(rp_pico::hal::pio::Buffers::OnlyTx) + .out_pins(2, 12) // Red0 is GPIO2, Blue3 is GPIO13 + .autopull(true) + .out_shift_direction(rp_pico::hal::pio::ShiftDirection::Right) + .pull_threshold(32) // We read all 32-bits in each FIFO word + .build(sm1); + pixel_sm.set_pindirs((2..=13).map(|x| (x, rp_pico::hal::pio::PinDir::Output))); // Read from the timing buffer and write to the timing FIFO. We get an // IRQ when the transfer is complete (i.e. when line has been fully @@ -453,8 +448,8 @@ pub fn init( // Enable the interrupts (DMA_PERIPH has to be set first) cortex_m::interrupt::enable(); - pico::hal::pac::NVIC::unpend(pico::hal::pac::Interrupt::DMA_IRQ_0); - pico::hal::pac::NVIC::unmask(pico::hal::pac::Interrupt::DMA_IRQ_0); + crate::pac::NVIC::unpend(crate::pac::Interrupt::DMA_IRQ_0); + crate::pac::NVIC::unmask(crate::pac::Interrupt::DMA_IRQ_0); } debug!("IRQs enabled"); @@ -470,11 +465,24 @@ pub fn init( // cannot be reconfigured at a later time, but they do keep on running // as-is. - static mut CORE1_STACK: [usize; 1024] = [0usize; 1024]; + let core1_stack: &'static mut [usize] = unsafe { + extern "C" { + static mut _core1_stack_bottom: usize; + static mut _core1_stack_len: usize; + } + core::slice::from_raw_parts_mut( + &mut _core1_stack_bottom as *mut _, + &mut _core1_stack_len as *const _ as usize / 4, + ) + }; - unsafe { - multicore_launch_core1_with_stack(core1_main, &mut CORE1_STACK, ppb, fifo, psm); - } + debug!( + "Core 1 stack: {:08x}, {} bytes", + core1_stack.as_ptr(), + core1_stack.len() + ); + + multicore_launch_core1_with_stack(core1_main, core1_stack, ppb, fifo, psm); debug!("Core 1 running"); } @@ -489,9 +497,9 @@ extern "C" fn core1_wrapper(entry_func: extern "C" fn() -> u32, _stack_base: *mu fn multicore_launch_core1_with_stack( main_func: unsafe extern "C" fn() -> u32, stack: &mut [usize], - ppb: &mut pico::hal::pac::PPB, - fifo: &mut pico::hal::sio::SioFifo, - psm: &mut pico::hal::pac::PSM, + ppb: &mut crate::pac::PPB, + fifo: &mut rp_pico::hal::sio::SioFifo, + psm: &mut crate::pac::PSM, ) { debug!("Resetting CPU1..."); @@ -535,8 +543,8 @@ fn multicore_launch_core1_with_stack( (CORE1_ENTRY_FUNCTION.as_ptr() as usize as u32) + 1, ]; - let enabled = pico::hal::pac::NVIC::is_enabled(pico::hal::pac::Interrupt::SIO_IRQ_PROC0); - pico::hal::pac::NVIC::mask(pico::hal::pac::Interrupt::SIO_IRQ_PROC0); + let enabled = crate::pac::NVIC::is_enabled(crate::pac::Interrupt::SIO_IRQ_PROC0); + crate::pac::NVIC::mask(crate::pac::Interrupt::SIO_IRQ_PROC0); 'outer: loop { for cmd in cmd_sequence.iter() { @@ -571,7 +579,7 @@ fn multicore_launch_core1_with_stack( } if enabled { - unsafe { pico::hal::pac::NVIC::unmask(pico::hal::pac::Interrupt::SIO_IRQ_PROC0) }; + unsafe { crate::pac::NVIC::unmask(crate::pac::Interrupt::SIO_IRQ_PROC0) }; } debug!("Waiting for Core 1 to start..."); @@ -706,7 +714,7 @@ impl RenderEngine { DMA_READY.store(false, Ordering::Relaxed); let current_line_num = CURRENT_DISPLAY_LINE.load(Ordering::Relaxed); if current_line_num == 0 { - debug!("Frame {}", self.frame_count); + trace!("Frame {}", self.frame_count); self.frame_count += 1; } @@ -809,30 +817,187 @@ impl TextConsole { /// /// Adjusts the current row and column automatically. Also understands /// Carriage Return and New Line bytes. - pub fn write_cp850_char(&self, cp850_char: u8) { + pub fn write_font_glyph(&self, font_glyph: u8) { // Load from global state let mut row = self.current_row.load(Ordering::Relaxed); let mut col = self.current_col.load(Ordering::Relaxed); let buffer = self.text_buffer.load(Ordering::Relaxed); if !buffer.is_null() { - self.write_at(cp850_char, buffer, &mut row, &mut col); + self.write_at(font_glyph, buffer, &mut row, &mut col); // Push back to global state self.current_row.store(row as u16, Ordering::Relaxed); self.current_col.store(col as u16, Ordering::Relaxed); } } - fn write_at(&self, cp850_char: u8, buffer: *mut u8, row: &mut u16, col: &mut u16) { - if cp850_char == b'\r' { + /// Moves the text cursor to the specified row and column. + /// + /// If a value is out of bounds, the cursor is not moved in that axis. + pub fn move_to(&self, row: u16, col: u16) { + if row < (NUM_TEXT_ROWS as u16) { + self.current_row.store(row, Ordering::Relaxed); + } + if col < (NUM_TEXT_COLS as u16) { + self.current_col.store(col, Ordering::Relaxed); + } + } + + /// Convert a Unicode Scalar Value to a font glyph. + /// + /// Zero-width and modifier Unicode Scalar Values (e.g. `U+0301 COMBINING, + /// ACCENT`) are not supported. Normalise your Unicode before calling + /// this function. + fn map_char_to_glyph(input: char) -> u8 { + // This fixed table only works for the default font. When we support + // changing font, we will need to plug-in a different table for each font. + match input { + '\u{0000}'..='\u{007F}' => input as u8, + '\u{00A0}' => 255, // NBSP + '\u{00A1}' => 173, // ¡ + '\u{00A2}' => 189, // ¢ + '\u{00A3}' => 156, // £ + '\u{00A4}' => 207, // ¤ + '\u{00A5}' => 190, // ¥ + '\u{00A6}' => 221, // ¦ + '\u{00A7}' => 245, // § + '\u{00A8}' => 249, // ¨ + '\u{00A9}' => 184, // © + '\u{00AA}' => 166, // ª + '\u{00AB}' => 174, // « + '\u{00AC}' => 170, // ¬ + '\u{00AD}' => 240, // SHY + '\u{00AE}' => 169, // ® + '\u{00AF}' => 238, // ¯ + '\u{00B0}' => 248, // ° + '\u{00B1}' => 241, // ± + '\u{00B2}' => 253, // ² + '\u{00B3}' => 252, // ³ + '\u{00B4}' => 239, // ´ + '\u{00B5}' => 230, // µ + '\u{00B6}' => 244, // ¶ + '\u{00B7}' => 250, // · + '\u{00B8}' => 247, // ¸ + '\u{00B9}' => 251, // ¹ + '\u{00BA}' => 167, // º + '\u{00BB}' => 175, // » + '\u{00BC}' => 172, // ¼ + '\u{00BD}' => 171, // ½ + '\u{00BE}' => 243, // ¾ + '\u{00BF}' => 168, // ¿ + '\u{00C0}' => 183, // À + '\u{00C1}' => 181, // Á + '\u{00C2}' => 182, // Â + '\u{00C3}' => 199, // Ã + '\u{00C4}' => 142, // Ä + '\u{00C5}' => 143, // Å + '\u{00C6}' => 146, // Æ + '\u{00C7}' => 128, // Ç + '\u{00C8}' => 212, // È + '\u{00C9}' => 144, // É + '\u{00CA}' => 210, // Ê + '\u{00CB}' => 211, // Ë + '\u{00CC}' => 222, // Ì + '\u{00CD}' => 214, // Í + '\u{00CE}' => 215, // Î + '\u{00CF}' => 216, // Ï + '\u{00D0}' => 209, // Ð + '\u{00D1}' => 165, // Ñ + '\u{00D2}' => 227, // Ò + '\u{00D3}' => 224, // Ó + '\u{00D4}' => 226, // Ô + '\u{00D5}' => 229, // Õ + '\u{00D6}' => 153, // Ö + '\u{00D7}' => 158, // × + '\u{00D8}' => 157, // Ø + '\u{00D9}' => 235, // Ù + '\u{00DA}' => 233, // Ú + '\u{00DB}' => 234, // Û + '\u{00DC}' => 154, // Ü + '\u{00DD}' => 237, // Ý + '\u{00DE}' => 232, // Þ + '\u{00DF}' => 225, // ß + '\u{00E0}' => 133, // à + '\u{00E1}' => 160, // á + '\u{00E2}' => 131, // â + '\u{00E3}' => 198, // ã + '\u{00E4}' => 132, // ä + '\u{00E5}' => 134, // å + '\u{00E6}' => 145, // æ + '\u{00E7}' => 135, // ç + '\u{00E8}' => 138, // è + '\u{00E9}' => 130, // é + '\u{00EA}' => 136, // ê + '\u{00EB}' => 137, // ë + '\u{00EC}' => 141, // ì + '\u{00ED}' => 161, // í + '\u{00EE}' => 140, // î + '\u{00EF}' => 139, // ï + '\u{00F0}' => 208, // ð + '\u{00F1}' => 164, // ñ + '\u{00F2}' => 149, // ò + '\u{00F3}' => 162, // ó + '\u{00F4}' => 147, // ô + '\u{00F5}' => 228, // õ + '\u{00F6}' => 148, // ö + '\u{00F7}' => 246, // ÷ + '\u{00F8}' => 155, // ø + '\u{00F9}' => 151, // ù + '\u{00FA}' => 163, // ú + '\u{00FB}' => 150, // û + '\u{00FC}' => 129, // ü + '\u{00FD}' => 236, // ý + '\u{00FE}' => 231, // þ + '\u{00FF}' => 152, // ÿ + '\u{0131}' => 213, // ı + '\u{0192}' => 159, // ƒ + '\u{2017}' => 242, // ‗ + '\u{2500}' => 196, // ─ + '\u{2502}' => 179, // │ + '\u{250C}' => 218, // ┌ + '\u{2510}' => 191, // ┐ + '\u{2514}' => 192, // └ + '\u{2518}' => 217, // ┘ + '\u{251C}' => 195, // ├ + '\u{2524}' => 180, // ┤ + '\u{252C}' => 194, // ┬ + '\u{2534}' => 193, // ┴ + '\u{253C}' => 197, // ┼ + '\u{2550}' => 205, // ═ + '\u{2551}' => 186, // ║ + '\u{2554}' => 201, // ╔ + '\u{2557}' => 187, // ╗ + '\u{255A}' => 200, // ╚ + '\u{255D}' => 188, // ╝ + '\u{2560}' => 204, // ╠ + '\u{2563}' => 185, // ╣ + '\u{2566}' => 203, // ╦ + '\u{2569}' => 202, // ╩ + '\u{256C}' => 206, // ╬ + '\u{2580}' => 223, // ▀ + '\u{2584}' => 220, // ▄ + '\u{2588}' => 219, // █ + '\u{2591}' => 176, // ░ + '\u{2592}' => 177, // ▒ + '\u{2593}' => 178, // ▓ + '\u{25A0}' => 254, // ■ + _ => b'?', + } + } + + /// Put a single character at a specified point on screen. + /// + /// The character is relative to the current font. + fn write_at(&self, font_glyph: u8, buffer: *mut u8, row: &mut u16, col: &mut u16) { + if font_glyph == b'\r' { *col = 0; - } else if cp850_char == b'\n' { + } else if font_glyph == b'\n' { *col = 0; *row += 1; } else { let offset = (*col as usize) + (NUM_TEXT_COLS * (*row as usize)); // Note (safety): This is safe as we bound `col` and `row` - unsafe { buffer.add(offset).write_volatile(cp850_char) }; + unsafe { buffer.add(offset).write_volatile(font_glyph) }; *col += 1; } if *col == (NUM_TEXT_COLS as u16) { @@ -870,10 +1035,8 @@ impl core::fmt::Write for &TextConsole { let buffer = self.text_buffer.load(Ordering::Relaxed); if !buffer.is_null() { - // Convert a String (a collection of bytes which are valid UTF-8) to CP850 (very badly) for ch in s.chars() { - let b = if (ch as u32) < 127 { ch as u8 } else { b'?' }; - + let b = TextConsole::map_char_to_glyph(ch); self.write_at(b, buffer, &mut row, &mut col); } From 2a7ab1ca702ff164293982b8ecb8c81e63c90631 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Sun, 13 Mar 2022 22:43:39 +0000 Subject: [PATCH 38/41] Updates to how we boot. 1. Re-arranged it so we use the top of RAM and the bottom of Flash. 2. Updated all the crates. 3. Changed rendering to more accurately map Unicode to font glyphs. 4. Add space for attributes (not currently rendered) 5. Print OS memory locations, ready for OS booting. --- .cargo/config.toml | 2 +- src/main.rs | 41 ++++++++++------------ src/vga/mod.rs | 86 ++++++++++++++++++++++++++++++++++++---------- 3 files changed, 87 insertions(+), 42 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 919b29e..2ae0730 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -2,7 +2,7 @@ # This will make a UF2 and copy it to the RP2040's Mass Storage Device bootloader # runner = "elf2uf2-rs -d" # This will flash over SWD with any compatible probe it finds. You need 0.3.1 or higher for RP2040 support. -runner = "probe-run --chip RP2040" +runner = "probe-run --chip RP2040 --measure-stack" rustflags = [ # This is needed if your flash or ram addresses are not aligned to 0x10000 in memory.x diff --git a/src/main.rs b/src/main.rs index 824c19a..283dc73 100644 --- a/src/main.rs +++ b/src/main.rs @@ -74,6 +74,13 @@ const GIT_VERSION: &str = git_version!(); /// Create a new Text Console static TEXT_CONSOLE: vga::TextConsole = vga::TextConsole::new(); +extern "C" { + static mut _flash_os_start: u32; + static mut _flash_os_len: u32; + static mut _ram_os_start: u32; + static mut _ram_os_len: u32; +} + // ----------------------------------------------------------------------------- // Functions // ----------------------------------------------------------------------------- @@ -217,8 +224,9 @@ fn main() -> ! { &mut pac.PSM, ); - TEXT_CONSOLE.set_text_buffer(unsafe { &mut vga::CHAR_ARRAY }); + TEXT_CONSOLE.set_text_buffer(unsafe { &mut vga::GLYPH_ATTR_ARRAY }); + // A crude way to clear the screen for _col in 0..vga::NUM_TEXT_ROWS { println!(); } @@ -244,29 +252,18 @@ fn main() -> ! { println!(); println!("Searching for Neotron OS..."); - extern "C" { - static mut _flash_os_start: u32; - static mut _flash_os_len: u32; - static mut _ram_os_start: u32; - static mut _ram_os_len: u32; - } + let flash_os_start = unsafe { &mut _flash_os_start as *mut u32 as usize }; + let flash_os_len = unsafe { &mut _flash_os_len as *mut u32 as usize }; + let ram_os_start = unsafe { &mut _ram_os_start as *mut u32 as usize }; + let ram_os_len = unsafe { &mut _ram_os_len as *mut u32 as usize }; - let flash_os_start = unsafe { - &mut _flash_os_start as *mut u32 as usize - }; - let flash_os_len = unsafe { - &mut _flash_os_len as *mut u32 as usize - }; - let ram_os_start = unsafe { - &mut _ram_os_start as *mut u32 as usize - }; - let ram_os_len = unsafe { - &mut _ram_os_len as *mut u32 as usize - }; - - println!("OS Flash is {:08x}, {} bytes", flash_os_start, flash_os_len); - println!("OS RAM is {:08x}, {} bytes", ram_os_start, ram_os_len); + println!( + "OS Flash @ 0x{:08x}, {} bytes", + flash_os_start, flash_os_len + ); + println!("OS RAM @ 0x{:08x}, {} bytes", ram_os_start, ram_os_len); + // A dummy loop so we know it's running let mut x: u32 = 0; loop { print!("\rx = {}", x); diff --git a/src/vga/mod.rs b/src/vga/mod.rs index e723d86..a130890 100644 --- a/src/vga/mod.rs +++ b/src/vga/mod.rs @@ -66,7 +66,7 @@ struct RenderEngine { pub struct TextConsole { current_col: AtomicU16, current_row: AtomicU16, - text_buffer: AtomicPtr, + text_buffer: AtomicPtr, } /// Describes one scan-line's worth of pixels, including the length word required by the Pixel FIFO. @@ -133,6 +133,22 @@ pub struct RGBColour(u16); #[derive(Copy, Clone, PartialEq, Eq)] pub struct RGBPair(u32); +/// Represents a glyph in the current font. +#[repr(transparent)] +#[derive(Copy, Clone, PartialEq, Eq)] +pub struct Glyph(u8); + +/// Represents VGA format foreground/background attributes. +#[repr(transparent)] +#[derive(Copy, Clone, PartialEq, Eq)] +pub struct Attr(u8); + +/// Represents a glyph/attribute pair. This is what out text console is made +/// out of. They work in exactly the same way as IBM PC VGA. +#[repr(transparent)] +#[derive(Copy, Clone, PartialEq, Eq, Default)] +pub struct GlyphAttr(u16); + // ----------------------------------------------------------------------------- // Static and Const Data // ----------------------------------------------------------------------------- @@ -206,11 +222,12 @@ static mut PIXEL_DATA_BUFFER_ODD: LineBuffer = LineBuffer { /// This is our text buffer. /// /// This is arranged as `NUM_TEXT_ROWS` rows of `NUM_TEXT_COLS` columns. Each -/// item is an index into `font::FONT_DATA` (or a Code-Page 850 character). +/// item is an index into `font::FONT_DATA` (or a Code-Page 850 character) +/// plus an 8-bit attribute. /// /// Written to by Core 0, and read from by `RenderEngine` running on Core 1. -pub static mut CHAR_ARRAY: [u8; NUM_TEXT_COLS as usize * NUM_TEXT_ROWS as usize] = - [0u8; NUM_TEXT_COLS as usize * NUM_TEXT_ROWS as usize]; +pub static mut GLYPH_ATTR_ARRAY: [GlyphAttr; NUM_TEXT_COLS as usize * NUM_TEXT_ROWS as usize] = + [GlyphAttr(0); NUM_TEXT_COLS as usize * NUM_TEXT_ROWS as usize]; /// Core 1 entry function. /// @@ -740,7 +757,7 @@ impl RenderEngine { // state. At least our platform is fixed, so we can simply // test if it works, for some given version of the Rust compiler. let row_slice = unsafe { - &CHAR_ARRAY[(text_row * NUM_TEXT_COLS)..((text_row + 1) * NUM_TEXT_COLS)] + &GLYPH_ATTR_ARRAY[(text_row * NUM_TEXT_COLS)..((text_row + 1) * NUM_TEXT_COLS)] }; // Every font look-up we are about to do for this row will // involve offsetting by the row within each glyph. As this @@ -754,14 +771,15 @@ impl RenderEngine { let mut px_idx = 0; // Convert from characters to coloured pixels, using the font as a look-up table. - for ch in row_slice.iter() { - let index = (*ch as isize) * 16; + for glyphattr in row_slice.iter() { + let index = (glyphattr.glyph().0 as isize) * 16; // Note (unsafe): We use pointer arithmetic here because we // can't afford a bounds-check on an array. This is safe // because the font is `256 * width` bytes long and we can't // index more than `255 * width` bytes into it. let mono_pixels = unsafe { *font_ptr.offset(index) } as usize; - // Convert from eight mono pixels in one byte to four RGB pairs + // Convert from eight mono pixels in one byte to four RGB + // pairs. Hopefully the `& 3` elides the panic calls. unsafe { core::ptr::write_volatile( scan_line_buffer_ptr.offset(px_idx), @@ -808,7 +826,10 @@ impl TextConsole { /// Update the text buffer we are using. /// /// Will reset the cursor. The screen is not cleared. - pub fn set_text_buffer(&self, text_buffer: &'static mut [u8; NUM_TEXT_ROWS * NUM_TEXT_COLS]) { + pub fn set_text_buffer( + &self, + text_buffer: &'static mut [GlyphAttr; NUM_TEXT_ROWS * NUM_TEXT_COLS], + ) { self.text_buffer .store(text_buffer.as_mut_ptr(), Ordering::Relaxed) } @@ -817,14 +838,14 @@ impl TextConsole { /// /// Adjusts the current row and column automatically. Also understands /// Carriage Return and New Line bytes. - pub fn write_font_glyph(&self, font_glyph: u8) { + pub fn write_font_glyph(&self, glyph: Glyph) { // Load from global state let mut row = self.current_row.load(Ordering::Relaxed); let mut col = self.current_col.load(Ordering::Relaxed); let buffer = self.text_buffer.load(Ordering::Relaxed); if !buffer.is_null() { - self.write_at(font_glyph, buffer, &mut row, &mut col); + self.write_at(glyph, buffer, &mut row, &mut col); // Push back to global state self.current_row.store(row as u16, Ordering::Relaxed); self.current_col.store(col as u16, Ordering::Relaxed); @@ -848,10 +869,10 @@ impl TextConsole { /// Zero-width and modifier Unicode Scalar Values (e.g. `U+0301 COMBINING, /// ACCENT`) are not supported. Normalise your Unicode before calling /// this function. - fn map_char_to_glyph(input: char) -> u8 { + fn map_char_to_glyph(input: char) -> Glyph { // This fixed table only works for the default font. When we support // changing font, we will need to plug-in a different table for each font. - match input { + let index = match input { '\u{0000}'..='\u{007F}' => input as u8, '\u{00A0}' => 255, // NBSP '\u{00A1}' => 173, // ¡ @@ -982,22 +1003,27 @@ impl TextConsole { '\u{2593}' => 178, // ▓ '\u{25A0}' => 254, // ■ _ => b'?', - } + }; + Glyph(index) } /// Put a single character at a specified point on screen. /// /// The character is relative to the current font. - fn write_at(&self, font_glyph: u8, buffer: *mut u8, row: &mut u16, col: &mut u16) { - if font_glyph == b'\r' { + fn write_at(&self, glyph: Glyph, buffer: *mut GlyphAttr, row: &mut u16, col: &mut u16) { + if glyph.0 == b'\r' { *col = 0; - } else if font_glyph == b'\n' { + } else if glyph.0 == b'\n' { *col = 0; *row += 1; } else { let offset = (*col as usize) + (NUM_TEXT_COLS * (*row as usize)); // Note (safety): This is safe as we bound `col` and `row` - unsafe { buffer.add(offset).write_volatile(font_glyph) }; + unsafe { + buffer + .add(offset) + .write_volatile(GlyphAttr::new(glyph, Attr(0))) + }; *col += 1; } if *col == (NUM_TEXT_COLS as u16) { @@ -1018,7 +1044,11 @@ impl TextConsole { for blank_col in 0..NUM_TEXT_COLS { let offset = (blank_col as usize) + (NUM_TEXT_COLS * (*row as usize)); - unsafe { buffer.add(offset).write_volatile(b' ') }; + unsafe { + buffer + .add(offset) + .write_volatile(GlyphAttr::new(Glyph(b' '), Attr(0))) + }; } } } @@ -1266,6 +1296,24 @@ impl RGBPair { } } +impl GlyphAttr { + /// Make a new glyph/attribute pair. + pub const fn new(glyph: Glyph, attr: Attr) -> GlyphAttr { + let value: u16 = (glyph.0 as u16) + ((attr.0 as u16) << 8); + GlyphAttr(value) + } + + /// Get the glyph component of this pair. + pub const fn glyph(self) -> Glyph { + Glyph(self.0 as u8) + } + + /// Get the attribute component of this pair. + pub const fn attr(self) -> Attr { + Attr((self.0 >> 8) as u8) + } +} + // ----------------------------------------------------------------------------- // End of file // ----------------------------------------------------------------------------- From c3af21f45e541a5d9cbd5cab50554942f9af5450 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Sun, 13 Mar 2022 22:46:39 +0000 Subject: [PATCH 39/41] Don't build debug - it doesn't fit. --- .github/workflows/rust.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 2bf88ba..30c3ff1 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -34,8 +34,6 @@ jobs: override: true - name: Install Rust targets for ${{ matrix.rust }} run: rustup target install --toolchain=${{ matrix.rust }} thumbv6m-none-eabi - - name: Build - run: cargo build --verbose - name: Build Release run: cargo build --release --verbose - name: Clippy From 0bc676022dad5e0edda8704490c5960c69de3c7b Mon Sep 17 00:00:00 2001 From: Jonathan Pallant Date: Fri, 18 Mar 2022 19:34:40 +0000 Subject: [PATCH 40/41] Can boot OS v0.1.0. (#9) * Can boot OS v0.1.0. * Clean up workflows --- .github/workflows/build.yml | 62 +++++ .github/workflows/clippy.yml | 26 +++ .github/workflows/format.yml | 20 +- .github/workflows/release.yml | 45 ---- .github/workflows/rust.yml | 42 ---- Cargo.toml | 6 +- README.md | 11 +- build.rs | 18 ++ memory.x | 8 + src/flash1002.bin | Bin 0 -> 19248 bytes src/flash1002.txt | 15 ++ src/main.rs | 426 ++++++++++++++++++++++++++-------- 12 files changed, 476 insertions(+), 203 deletions(-) create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/clippy.yml delete mode 100644 .github/workflows/release.yml delete mode 100644 .github/workflows/rust.yml create mode 100755 src/flash1002.bin create mode 100644 src/flash1002.txt diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..4f7067e --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,62 @@ +name: Build + +on: [push, pull_request] + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + runs-on: ubuntu-latest + continue-on-error: ${{ matrix.experimental || false }} + strategy: + matrix: + # All code should be running on stable now + rust: [nightly, stable] + include: + # Nightly is only for reference and allowed to fail + - rust: nightly + experimental: true + steps: + - name: Checkout Code + uses: actions/checkout@v3 + with: + submodules: true + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.rust }} + override: true + target: thumbv6m-none-eabi + + - name: Build Code + run: cargo build --release --verbose + + - name: Get Branch Name + if: github.event_name == 'push' && startswith(github.ref, 'refs/tags/') + id: branch_name + run: | + echo ::set-output name=SOURCE_TAG::${GITHUB_REF#refs/tags/} + + - name: Create Release + if: github.event_name == 'push' && startswith(github.ref, 'refs/tags/') + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: Release ${{ steps.branch_name.outputs.SOURCE_TAG }} + draft: false + prerelease: false + + - name: Upload files to Release + if: github.event_name == 'push' && startswith(github.ref, 'refs/tags/') + uses: softprops/action-gh-release@v1 + with: + files: | + target/thumbv6m-none-eabi/release/neotron-pico + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml new file mode 100644 index 0000000..c98669d --- /dev/null +++ b/.github/workflows/clippy.yml @@ -0,0 +1,26 @@ +name: Clippy + +on: [push, pull_request] + +env: + CARGO_TERM_COLOR: always + +jobs: + clippy-check: + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v3 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + components: clippy + target: thumbv6m-none-eabi + + - name: Run Clippy + uses: actions-rs/clippy-check@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + args: --all-features --target=thumbv6m-none-eabi diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 53d366d..0c586df 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -3,13 +3,17 @@ name: Format on: [push, pull_request] jobs: - check: - + format-check: runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 - - name: Add Tool - run: rustup component add rustfmt - - name: Check Format - run: cargo fmt -- --check + - name: Checkout Code + uses: actions/checkout@v3 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + components: rustfmt + + - name: Check Format + run: cargo fmt -- --check diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index c67332f..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,45 +0,0 @@ -on: - push: - tags: - - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 - -name: Upload Release - -jobs: - build: - name: Upload Release - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v1 - with: - submodules: true - - name: Get Branch Name - id: branch_name - run: | - echo ::set-output name=SOURCE_NAME::${GITHUB_REF#refs/*/} - echo ::set-output name=SOURCE_BRANCH::${GITHUB_REF#refs/heads/} - echo ::set-output name=SOURCE_TAG::${GITHUB_REF#refs/tags/} - - name: Install GCC ARM - run: sudo apt-get update && sudo apt-get install -y gcc-arm-none-eabi - - name: Add Target - run: rustup target add thumbv6m-none-eabi - - name: Build Release - run: cargo build --release --verbose - - name: Create Release - id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: ${{ github.ref }} - release_name: Release ${{ steps.branch_name.outputs.SOURCE_TAG }} - draft: false - prerelease: false - - name: Upload files to Release - uses: softprops/action-gh-release@v1 - with: - files: | - target/thumbv6m-none-eabi/release/neotron-pico-bios - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml deleted file mode 100644 index 30c3ff1..0000000 --- a/.github/workflows/rust.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: Build - -on: [push, pull_request] - -env: - CARGO_TERM_COLOR: always - -jobs: - build: - continue-on-error: ${{ matrix.experimental || false }} - strategy: - matrix: - # All generated code should be running on stable now - rust: [nightly, stable] - include: - # Nightly is only for reference and allowed to fail - - rust: nightly - experimental: true - os: - - ubuntu-latest - - macOS-latest - - windows-latest - runs-on: ${{ matrix.os }} - steps: - - name: Checkout - uses: actions/checkout@v2 - with: - submodules: true - - name: Build - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: ${{ matrix.rust }} - override: true - - name: Install Rust targets for ${{ matrix.rust }} - run: rustup target install --toolchain=${{ matrix.rust }} thumbv6m-none-eabi - - name: Build Release - run: cargo build --release --verbose - - name: Clippy - uses: actions-rs/clippy-check@v1 - with: - token: ${{ secrets.GITHUB_TOKEN }} diff --git a/Cargo.toml b/Cargo.toml index 4d21688..6b7cbd3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,8 +14,8 @@ cortex-m = "0.7" rp-pico = "0.3" # Cortex-M run-time (or start-up) code cortex-m-rt = "0.7" -# The BIOS API we export to the OS -neotron-common-bios = { git = "https://github.com/Neotron-Compute/Neotron-Common-BIOS.git" } +# The BIOS to OS API +neotron-common-bios = "0.1.0" # For time keeping/handling embedded-time = "0.12" # For the RP2040 bootloader @@ -28,8 +28,6 @@ defmt = "0.3" defmt-rtt = "0.3" # Send panics to the debugger panic-probe = "0.2" -# Fetches git hashes from git at compile time -git-version = "0.3" # RP2040 PIO assembler pio = "0.2" # Macros for RP2040 PIO assembler diff --git a/README.md b/README.md index c5e9444..8faed6b 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,16 @@ user@host ~ $ cargo install probe-rs 4. Power on your Neotron Pico. -5. Build and load the Neotron BIOS, and view the debug output stream, with `cargo run --release`: +5. Build the Neotron OS + +We use the "neotron-os-pico.ld" linker script to link it at `0x1002_0000`. + +```console +user@host ~/neotron-os $ cargo build --release +user@host ~/neotron-os $ arm-none-eabi-objcopy -O binary ./target/thumbv6m-none-eabi/release/flash1002 ../neotron-pico-bios/src/flash1002.bin +``` + +6. Build and load the Neotron BIOS, and view the debug output stream, with `cargo run --release`: ```console user@host ~/neotron-pico-bios $ DEFMT_LOG=debug cargo run --release diff --git a/build.rs b/build.rs index 28225a5..b531146 100644 --- a/build.rs +++ b/build.rs @@ -28,4 +28,22 @@ fn main() { // here, we ensure the build script is only re-run when // `memory.x` is changed. println!("cargo:rerun-if-changed=memory.x"); + + // Get git version + if let Ok(cmd_output) = std::process::Command::new("git") + .arg("describe") + .arg("--all") + .arg("--dirty") + .arg("--long") + .output() + { + let git_version = std::str::from_utf8(&cmd_output.stdout).unwrap(); + println!( + "cargo:rustc-env=BIOS_VERSION={} (git:{})", + env!("CARGO_PKG_VERSION"), + git_version.trim() + ); + } else { + println!("cargo:rustc-env=BIOS_VERSION={}", env!("CARGO_PKG_VERSION")); + } } diff --git a/memory.x b/memory.x index 6d55748..5ee66e8 100644 --- a/memory.x +++ b/memory.x @@ -68,4 +68,12 @@ SECTIONS { { KEEP(*(.boot2)); } > BOOT2 + + /* ### Neotron OS */ + .flash_os ORIGIN(FLASH_OS) : + { + KEEP(*(.flash_os)); + } > FLASH_OS } INSERT BEFORE .text; + + diff --git a/src/flash1002.bin b/src/flash1002.bin new file mode 100755 index 0000000000000000000000000000000000000000..eef2fd3659eb1de0a95aa746e521987e814adc8e GIT binary patch literal 19248 zcmc(`34ByVwm4jM_rBeobOMCiNvCtWv(VW)djOTD8!u@<47*Cy1c=^tAZY{mg8t@B z2TNsIWA>X;T6AYu@`+x8I ze&6rGuTslBb?Q{rsk2peV=iF{J%gS!O3cfGgvQu^hE5>);;7bkU;sx@m=M`8cZ`W3 z2OXgzWSFfXAQ{5}iH{hbpGNyLsD=aVYwa=ma%8ZCm$FfB7|JFw9G#z zXP>ulWR^r7a!GDKIo9(P87}rO^!cs(2HUy98n17qX%#A{8LFcnJ8I3za0hd1hJ>u8 zp4MQH>bt>LD})9;D9dlCd&r&?box2L(^?xOyDKxqn!CNTq~X&W^XzBBgCQAfIa<@uTD=tLg@zJ#%%YutM;+ULR!CB#F=cY&d9l zm(a(PR?l%5Qc;9HW)uv5 zI^HvYkK@c4+q9J;VWr3#9JWZrSlYzJcYZ&YuxN0>#ivf;8p0o3JS08l&-~=8>YO9m zdhH#cI=FTw!bpUS9c(-ANy-!`lt+Y-k}uNX6Y^>+8}+eBMES%iLgK=r%Jrc9B!zUQ zdMpY)NFGAjOjKIu$$Zn-=S6SjN;r?~qhh$%pdtsWGAed5(Vc6%y|@>>?YA-AChJo& z&J|+#V1@K|`4O*phCIkd5u1NX6v-|_9hr+_*x^o_CGBSyDL8jg3^`B)ON&&uM0dgH zWA~p&u4`fgfzJZ3%2ZdLWK>;~U)tb{C)OYMNdJPnql+17t_pa2bL*H0%rKI>T9%2h zTa@dk`rHbA5@Ie%baP>Mnq+${@M+)<`(h<8;C6^ocrkK~D{Nm1%!W`Gavqz9L*eB% zGP+1^3$T%)0298$&MQom2(VFy4*z!r=eQ*U>>bf{z>8zBdSNok_28TX-p>^pC`Mxi zFzSEBnCSB|8>PB5N|Eba3}vIfC%nQmY+G!|^`#=)6V*>-QJR8e`~C=hxh^h9Mv1P4 z^#rWVfcNuvg%|MNM-$Fy@O12<^raAN$~i`qHtydr)M|L6S=>-}Gt?61*(-@q)%1oY!TNX8n? zxk$O<&p7wrW2W#1D|u*Z0OcDytCb`NM;XGbV-LlJL0TMS+bMOMZBH7uuD9nYo!*T~ zj)bzx*P((hUOzXe7Nm`gK?=6=;V)Ykh%OJ``#AHskw5rM>*GwJneTpFa0y2#uD8I0 ztcJr!yk4a|o$h73UmtziHZuA*TWl0LI=$5ja{hQN%1?d$%|SJP_Q+WNtdV-a>5|@W zU651cp(Q>VVnW8Ftw)(GvnYhD>)AK?At5O~Q_s11i1$ zO1Xq{9$f1YhkR{b^!66pgX`is;b>SJFPXZu(kOg;f7H}PcPDvF5fuCNSxSc>`+%R{ zUTHHPHud5{v%+^Z2z2JFYj***l{TV~Qkcw>I;NF2y7$WWsQh1@{xFqt{d~j_ws_bu z%HlgvKG*3~_#Q#Bc$l!UZ_LLD+Dx83i?Wz*ls8jBqI<2!aniR3ux_&9JR|V2du_Z% zXokrqP%@75d zZY`58E;k-dQHgFQDhf>4aG2}elqT^1^cy{#XGmar&iiY8$h1=sCALf3QKYOo3o~({ z*fnQ|T!t_j84^j!)MfOr9lH?I!G%K{iOtF<*Z8dYp6hJ1WcE-8$$X-n8Ah4>;cV$n zRm)!*-Q$D2ceDJU;hgWB!Q?CV@bP7} zyukEtw_R2{ep&6~%W8SIUDofi+VRV3A756>``%^!E~_2CtoHG=+A6_Qfie!nh;IHc z71dU4$*3&18HRjA2F~aHml=D6W7AfE6F!<&GYXV;E@HW(a$s8j!1NV^{?qEve_BoJ zPe6aesC?VB{}WA#H}5OpAg`@cgYBudmBdH6LU-0w|4tP3>VX z^H!e3sG3B`ljz<~HBY+D#={zb#E9LeYJ6?#iR!p{eP+8I~?>sLEeC6ahEd%R}GZEc81#P`> zH0%}5h@!g=IY<}CS%s8vG?d!SrYSf-1uP9dBB($mW*r0DHg`c`>lb&Vub=ik3{;4mfLcRMW%vXqEur$65Dy z-%&o1O;IDrQcdswDF!kydcKcPmHhlI&?zoOUNGidD#MU37ydN zZDc^q;vOe6*@O9O44o*G>qv|wgiRfM$3Ynt)+>sPTsGjYkVuosV!Bm)OpDCjp`rrl`%fv?YVBRutesLX_n5FF|m>>r=kPir(+Ja7BJW(Q33iLe%52( zr03Z4`%^5NJ58N^CN)3-S##M0Tci2CP5j{*dK3psB_L3v1(rzz3lAxYN z$?{5Jx7RSN&6#;6K4;C!O46(>{qK-83;dJ+xm{#|9UPL*PqVA;AINv&cJmShiEJmp zQ!<44<6Jv(vh7Se9p-8%NM^9hOuEM;OMIB>pBVq-9~a+wVF~z1kamN=)Mg|sw?oqQ ze*XYi8?pIkQ@{rx+PyMv56NU017@SjbX(TNwJ!KuZg6BPI1L0`IK^{4P>uJAUd#XU?fil`7U!E54O$Ss#(}#d&WCU$!q0-&Yh}OOPH*+=`}cS z`nQmZ(r>d3des>C2@+S$g?D?YKE6BQ%mHs{?1&0hWUN=Mh0&MAuozO~Feu9+C0kd@ zvfm66@0R5)o?Dmxr%>c+u^(DUTp#f@)ObC^LXy|ei((ZLDx_mvITzjS<-6ZIgH*no z1{)upc~GVL4tSfCl>!U)57TpO$KHsJIgd1%Ky$wo!E1xeaTcEstT6xW6*^vG{ zTZi}k*flDVyHp7SjW|Y#MJk!QNKv3RD@KZQV2m8qZ{w^^)nlXjM0GEeZE6V0sp?56 zr>H|vo~cr~xr$|nhJzAKDxK?8YV{f|(khdiri7s;#2At784`T9pL)sD$gvB!np835 zXxPw&b4i8i;_7&pRrxFxjR;`*ri>!+(70fk#B}v}%Oz_}+kGOc`47mI9(x4V;r$Po zE;05o=n4(;r{0f-nQmp$a!ZnI()({E*C4P!IjID^piJ=Kk!XaIVu)&g`6VJqu3rPU z@&Kj5IHfXxO9VI`{)OP*#Knsj9|In)g>&&TV<5>K|0EXX|dxPs>>MW z4#@ULt^&nizli!o_!f?7dQ< zBA*FvD;ZgoIxx8lXQ;Jy^6+ji zXvWLF4B3;_u>K_htnTT3PzJ3kcCve?7lT~zWV|4t0*i9MYaVv5`Wh8a(WD0tq@ zx1J%wds=Dms_AaRL%077)6q&{EZ;Knax3ti=PAqyM3c{YtFK zfc>&Z(qX@BVH50i0_>X+_RS2t&BJb+`cuM|{#@zexEp3SBxyRByAt9skTjXQV<~Upu)gE4q9hzkAR#Buxo^TSS^b>A4 z6F>S#`CV+32s5yM+8oIU4g+_`mP0gz>ee(DQg)~%;N8`UdlB)_%ImQ7Lup956~^y? z@gtDBAdQZq9J1@&=zD`Gi_H1HzIb-7KQ3Cugqe5iyDv@=ULm4G= zwmZtD-;YvRSUNe1vufF)1hVV5oodf*V7^S))$I41O(QXi)8J(iZ9jg+T{Y&(y<4yjPbwq@ItO3rY zA%i$<3SPhL&%*lFrNPwzL3OPV_73{d>ZLc-*(9>3$DSH&&?AP08MXfVy^N%s-soyR z(>A!5Te4({Z-r?i_ytRR7CrvB7~+qQL!5ErnvIk3$Mqo7d7{l>RM%yt8U8HLk8Kjp z*)xNC?IaJ@u;d*{%p^U0J|0<@g;aM|9O5WISX^^Po*z7t|K6}W>+I+hma6Z>l5q)3 z)wi%v)3EHZ`|WKm<8YgE#bmT|rk`-SB!^l9?O%+997#(qj-P;t5C+Sf&!vX9=<(4N zYYeI%B|;Q+YIgA`>KunZO^cc?wek=(y*S=^AshCn0Ai#zNLt)fi=Td1>{Q#oy9cQ7 zgov1(KBHuqPl)z>kd&vlWpyF^;~TGgQraaDmfYEdOtjDU=Q-yu z72$NDK9YEppG8_JdW{-n4FgNCwmbF$|`Bt#| zdXIavV)uBj9l?jl?nB<%(?oZ&V6mbCr!uP*<@9>5Qr6h+2JOA3wHG3m2cbMmISA!a z<#i}0Dp?t`(uZ5Sy?N5PmSGUY zv^iRrfL6w*Y!H9g<+Ujdtt*+8rWL|b8{NGkl|Ds7jD5L>?cD7hh23;}v=}(Qqy+SO z2y8l#t5LG+;`m=c$CIG%$t4rx-TEDlrUz?aJ*p?yq+u?AEDDLW7jk>r9AI`5y3XcWY@Bl>egR(Ik3?zo#frRjwYz_~|Ca}aTVX%?9 zC&uM}fWI1J`bCgf6XOj~V`4<~Ihj93Mm2rwQe0C1Xe!Y1ONDR_1ilEoDaR?GyQ!WY zovxEIj#wq!6K{`9(rN^Z(Zyt!M(w>iPR;fWFK!!60t?#`rS$K2j(!NXZYQKRNS{FZ zE2K{$DbEm3?i@hi8-{YpVSGF(h@HjiD?A@t;N(4_QvB!*!M%ndC+|xQ5>BVen-VU4 z=??NnHEX8N7BtX%6GA1|2T5m=x**f&36Kj>SdgBgPI^^%Nu1EaG+*2P^VBn zc_=|C_M26dpFVm2G)LbvH=>45V*A;xHXPV z<3=VH=N(q3gOTWHPK(D@Umrh7L(6 zq^f2~P;=le#vH4Mb>J0utA}Mz#cz-deypFgk0-gA(d)D%w#fFo7rG?vZfQuG0Q_ij zJ@e<&tI+C0@)RTVEhgh!yPIG)_@2$__3QI z5DMi79#%;|?Gym=dANuE1<;reex=HA!#?u!lirV6_@UnZ`bdYm+JS*+Pc*E zLb`2N^R2t(8&!yaiRe46yBYjWZ>r=2yKKgHTX%V`k&#Q*-8Xpt)xL}4(d@f8o^RQA zdDJvK|Ih5ZKY%uX9o6l-?f+umJu_*&ez$#h^!)$CzUvr3G9jDVCHVk&RD75SIN?q= zLG!6Vw;;uT78Mx|ncyB9Zi%c+So5Q_xPVbIh6#xd+f*c)x(tG$J2{mHNi6D-4x|*G z)$xFTK}&Pag0Ai%yNO3!l2lmZ`D_Z)jbRod+H) z=Xp1G#u>ZR)0*#T$HT%{u4Uv4C`a%~!NU&m27v?Ji((fb^3JFs+$Qwd2o;e@!ryId zZ(=_i*$Gia zb61?GvQ*P2XHG*%-c7<}$@cSP?%t7ZFEz0-a2YTE=eCGpF zeCq?;>(Xr<4ZBe>XjWJ+54+%*jNdSC8>3Hu06L}jsSi->$qx(=DKo*&7`u%i#qI>p z2{>18GxuB^Z_)48wR-~($Tjz*Y`YueFOq3_Pv9iH01*8g$Zw)^p?%LnGE5yzhu%cL z%}-@fk2=A2tNbcg;!3E9bvZg@^~c)^zA{x%2hpJvu(GH}C&shB!aF=WbZwUl(I1CJ zCAv7!5%j6}7zv<-C^cT@b^U-P@c0&Tl4gZJ0kMxOz`lMKYCoTcVJ%twVw=tmXY!CnUegn{DPKH^y0YxD;8!Ow>>n8^rFnb`iW9M=xbOt zao_~$XZ8BPiNjzMe*ri(yD7eI1ZXPl#%4$wgmHCy%D_4ZiHAEoayZdxft%^j38Kd3 zAi#h56mAb1Y6!~#Nb&W9$VaP5YF&hOc+kCzu#+*xXE@uRF*(;{zY%sR-fH#>-&);c zHZnpU<6wQaeKj%u;aBt|EbTV~Y zhFIxJh-_Rr+?Ls$irUHJGlYy~gtJ)Pm^TAv-NV^oz8g*2oV0msGfG?baNXU(hx9pR zi>VS)=ZJhBJv>A5*xhA-0j!^uC~4{8x{#fGykK>K&<(H~f^(e7lOvdyX%TjZbYa|d zArm+*1isdV@nl^VOvOeLAvQAgo@_B0kCA9LlD1E~;7;g9MT?c~5x_o%D7bTNO!xdY z+0xZpS{G#6JsIFxyqlYF=Hb?K&(H83*0EgE$k|q_=co93VI@1v?-jB@dqp7Tdela$ zN#yOqIUCW-=jJKq+!Q6rnM_5z`3lb`$BysF-*JPF=>2rZO6y8Q^i|4FtquU}N4K9G z?D8fnv(yi7pP?Sxk+)-o?~@%T1wSHs3*`klO{unlT1a1RAyxj$n1o}LNyO~E~ zB+NQK6H3~-qt^I4%0PetAvjZSyI*I=N#YA+7 z#jymC5M&qKkAnu&!jQ4k1TUtL1MVCmY$xq!B5+;Rr6)BG-(=0(DI` z^bIiK&uTvRw|R+S3;e{x9Yg%yf)vxksSpph z!Cm8n6A9AE!M*k+-X#kOpTZW#aF*77aKa4j`|K;cD;5s15cP?15^~vN=^&-k6kB(# zOu1%{U@K)5CS$08R$N((iV8@jG>cGcAwIb&i-qV!o1Mu;Bk!oF z@YYytnF!v^?AV)dAM<97Ei9&|hOuad8JRSDFbuXM+=#wj!@)dH`nls5$2UUEswVck zYh2(<%vSyY{r&*`WZQT3`<>qJgx+s4^eX@^#IEF+m~I2?=J2536w$`eZe5=NSX=*q z4qF1SDKTvX6bnNPFAn{aY0$@Y=tp$uRDk}z!u3DyS5>wjWz#_>`huL=|IpH0iS9nK z`0x^(H8aRYYl8><9E_hDI}B8_ux|+xmo9|Oo!V@T2l<1B&L09^q5posdtv-96WUA< zEU^F`CDLr=fyrLAw#)nM)BEi5r$}0_5$IJIyUSlHX=5eFpcQ6oKKO4nQ)Aqv)4p9F z<5_@CiIqy)7${aXS_^YEAKZKiHmJjH)?pt3Sh$5A<@%fa-&3iB4A}B4+;lQ(b4{|N z891{UZeB>ZFRJjk!G5{z>!pr;NaF}be?tJ4?J3YCm2mf0WPsAsl`ND}B35uv`E zgVeDcRJbrUOCqB$d6+2rUTE>>g9z?3jFO;`@Xl}8O>{OQ+Jg@T?Qol!Nk*wwsa4F$ z@rgy0vfk#gc6+~!p&sHe?jxh~uKB3t(Uzu`f)<=rP%qaPrP~gfBNf`*BJh&3vs|BB znLf8_nA(`RZTVmd zGj|Evs6%r)^xFV^Gz3eNkR7m#alEMJ$0DN6QaFWEM|m#Fs=^J+6WAcMu*b? z7WdE8VUGiBTpA`TvMmmqJPp>O!y0wiPJl(Ri=$LO2RgO^EFX8lz+=<- z^J%D0@u;TX@6mmYxYeEQH-K$B)%;jV#v_MNEvF&y{c$EWBoPNh#_cxxd~t-&A6%a7n2W(PPRtjTmLJ zeyQxM-!Jc~e^q|I-r}~DSmu#=pP!x>ADYl?sW>%%nWV-D@7BQD9ScGvPNS&}UV$4W zN*W%da#KcJOcKQjNrXqZTd-#AnWPc!nG{hHfi|`IxD!+_K-Jmc>83DAAV-CL=S%nrOI^e`^O_x_i$>8MD{-6d)RtXAR>EN z9H!dQp@j-ATJFM(@KFZkOIv5?tHBTNpDfYCuR_U?zQ*=2tbyk3YCE}qz?a(LMI-I%eqpzen1@dPL#5%D zCv2D$bEJEGhE6I>dFs7PnCwIa{2@`{qCPtteWmWX)h`H-c!l9c`zyj)du3};kcVGL zScV_AAG%aC44<ESxu8Duz9wp_tpw-$bA3N>{yQ9{g%R*yDZa^fomyZ}S;W=RW5DCHKKEsG>uu z@CywIetqM6Sa=yM<`7G@-=g5W);Hk|KeT_teFoq@Ugti;H16Xsao^3uhH2cVwa2+{ z)vyL1=RW=t_tgM&ocpx4Z@BLY;68_xp!48@*lO5;)u-U?HN4$}p(nLARoLf!@XXANG9f*qP|mJD;O#y$Q{t{jm$)nA z_4YHSd8YYk@{D>zw`HE8o#`1CELZLGzH|ES(ReJR4P;EonXA2IX1ZsQ6%|?(Mk)#3 zVS5BTw0O===Hz7l*O8TJDN-NE6I^6@6Lq{gICJ?;KGdVh2X0vVOY46L{qGkesUGbm z&J2CMEy{5rH>kZaG!9#X>mg?+a}sjxh2mdF;`cP#+h)ROz8`JNlOs$7MUD| zcde2pzh{|_!P|hjXBhq|@4Pdlq02k}4DQ&)bpe+P64Bw~z%LfNyqOX@LrTZI*^q+- z>L|odX|L+eFwz(3Ml0@_g6Kh?2t@A`oQwnk^*k>e_L!w!&)T&6y~)F+AlKt#yCiMY z7f!n+ZQSQ!y!gn^oq28)6_TeM09h){Qnzp|Y3uD7BJL181lSY=oLHp&!tD*PP4mzj zfYG@^y?95%=IxCLZO6JFf-v;m zF!QhL#=08=G4ii?_CHEZ({~u(7dtOXHS}a!XOm)4B;0R0&5Dbt`YEG+_`54l_2Zg+{h)LrH-cUQP8-Bs>t_uLY9Nl8g*Nm)sGNkvIz zNmWU8$=p(RX-R2mX<2D`X+>#eX;o=;>D)4RSxH%GSy@?mSw&f8Syfqe+1zq>c}aO` zd0BaRc}00;c~yCJ`P>S3MM*_zMOj68MMXtrMO8(0#oS7FWl3deWm#o;WkqFWWmRQ$ z<=iTFRY_H8RasSeRYg^0RaI4W)!b@#bxCz;by;peb?Y`+tcNkQJ!^s733;yg zZ!&P41{P?#RjiNqY;4*ziScQG(->$9K8`QWbo$6!#fEL7&W+GXub@-VN2B*+$jy+i zFDfd!al?*nQ_QOI_hp=~(N~X<8SHr@zzdLT{X@Wa+Iid1cx#|ZV@!=K7eY1gJptOE zhVR-MADyIe3SPtC)VvMO@J6|zC4Rc#EY24LuufZHVp{{78b#5ikpw5Ld1Hen0G3VW zxjHpdw6wxlIWR^a9ykr8vH)=p4Xm`}rlLbV%QV-QE<~*u1H+ zIM@u$PvQ{#P+W6a%5!!P5JMSiBG&S4O zZ~p$jKk?)b{^Qw{+4c-+@$zf0yP@u;fB(TVFP?h+t+CI(Y}xw2w(W;9f@vHaQ_*0WrqWlr&5{@T=Bb>#x@)#1phTerV_Qi+{DGkS3X zi8niUzDw;~#HCS8Qrlw*p-1TqLt7eYSl(1 z!G&I>d0{@uGu7aHnV4-xJ9z_jy&<$qu<xTa%{zSZiRr)M3N_|NDMI58f6Tqn0G-v)*%ddT)ukS~9?e94B z(BQA`d5U2Tl~-Q1_6sFO%@iuD)~@@(@h6@;U zPYw(zPdwSw+;NlhE}EnY$y-QVTol@wPL?Fvs4Rn>&Zieq z33;L8OcvZhySQ@Wa$j4OA%*9J`B%>+H*yAd3hf}%XzZz`meR!(&l*^dIEOMBD#>|t zDodHzT7OlUxr{C1__myt%UxVvN@|X6=4``q7+{gv%JNJNH^;EuxL{!(GoR*}6%3{= zBpuqn!Cu4hp+|0VUTx$V^Ne{6Us*uS4*l|qO{+{b241>4t%h4=_P4PT+?_1;SCI)E z&&*}{wn}U0=Qy#{ysPJy?Z(gx`<87q?=F5IwsY}=zuY;O&7l>F+ zbJ--1#sfb*&+Y!tJi|kyZDoZdggDv`?4`a(n@Iy}X|G>w*mgze@BCISI7`B3W^QMMv4RM+MTX-i6stGNHq~%lfbx_uH*;IWcl&^xM z*xwZk+hrj8T=3v8)&CLy)F2hN~{o0nsty>#z zZM>|#;$LvLWdg2-rkla|1*LvWvzR>3h=$}*p=dNiS7Zn5y-T(|A);xFuuWn;IU zz4qE>M{V!357wgB8dfyM9$bOmaI8e?*;Ved4Qrlw@4>aohiBJ{sA=68o>+&1h((3q zoDuLJ*BITiEVvN_CqZB;6Wi0)8|N7eSfH>0}kYK%xi`$Pkn#u~Dnj0Dxx_DIl%@8gO%%C3(V*=R;o;^mGAwzzWQPDV8wm zST&MhP(rKQh`B)D)F|n=22&W2V-Byt1Z(0pV8UQzmJn%x596u?3?tG;oMphbP?!Ot z2rEI67Rn4o1}9=zcQT!@!+#HfSq>9M0|qsLw-Zi$2T2hI%#d%wA^^3lhLPYHp1|&m z63PvA8h$itB1D)ZCad8ibR_3-1QCUw;$v1DkR)F6ApE=ok@w+x_$@$yKosUhq86?y zV2apE(D;W$YO)#Ua8_dx>4v!x#2oB{l_v-j%&Qoe0ak*bVcvNJhyS82oA%_!0!AWW zc@H0?5ec)TTqK1b2K+44C*5b{)6@ z!jJ?siM)%^cZng$B3Nw1!eZc+DL@Edxs!nbV7-CUw4t<%8_<;qFQ8Uvf;m$( z#}TX@eu9jus8S9m;1n7s0;VJ#3k@8LucQ#Wg+=wDF;u)}EBI-}w>394Y?B+BiiGM`kMP#OHhKGoqK(a43QHO{ZmcYwJ9pEDxs4mEOUsIz8k@JZG&dE( z$1RX;2sCdkbQhHrx%HlRfc@GGatEx&#+Igr&0?c=Zs&o040Uj6w>PykHh}F57Nw{e zm>_SyT1;4Of&6nwn*G0~Y3uf2u(@Sh<0fBo(=CBpx3}n4@RuMrrsUE;puDK@4)9xb zxv-`wuqC*;af>#}CVvw+#(_=o&uetgS&Tc>1>&t>djmz{NAMnIHjMTE?_zA*5p2{v zVuU#Gb+tJ@0109QNOQYEgzMiz@J+L)pNEtWX=>82T5A_CgjH(V+PoPS>M+3RzQ-hd zF*qQa52N+{J+x{5$h5wz8e0Oe{*&Fcz`l!!1o;HfxnnlM< zxQo&Joean|yvyU8E?&E;K)eIk2b>a7-*nLhyfSZF!>wD3@6a0x-G#T_;@;3$-MBFq zd}rvd`2|yB%>!gK)K@od-KIHp4PsDJ72t(^12caTrgq_Szt6#=qkw~*hQmAgeJV(@ zvT^J7&D;JpNYW(M18YnLNzN916C)`B-pPjg6fezzas{McLi+Qx_V=Ou1*9FtgysJN DFiw~g literal 0 HcmV?d00001 diff --git a/src/flash1002.txt b/src/flash1002.txt new file mode 100644 index 0000000..780af24 --- /dev/null +++ b/src/flash1002.txt @@ -0,0 +1,15 @@ +This is Neotron OS version v0.1.0 +See https://github.com/neotron-compute/neotron-os + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 283dc73..73b3845 100644 --- a/src/main.rs +++ b/src/main.rs @@ -43,18 +43,20 @@ pub mod vga; // Imports // ----------------------------------------------------------------------------- +use core::fmt::Write; use cortex_m_rt::entry; use defmt::info; use defmt_rtt as _; use embedded_hal::digital::v2::OutputPin; use embedded_time::rate::*; -use git_version::git_version; +use neotron_common_bios as common; use panic_probe as _; use rp_pico::{ self, hal::{ self, pac::{self, interrupt}, + Clock, }, }; @@ -68,11 +70,35 @@ use rp_pico::{ // Static and Const Data // ----------------------------------------------------------------------------- -/// BIOS version -const GIT_VERSION: &str = git_version!(); +/// The BIOS version string +static BIOS_VERSION: &str = concat!("Neotron Pico BIOS version ", env!("BIOS_VERSION"), "\0"); -/// Create a new Text Console -static TEXT_CONSOLE: vga::TextConsole = vga::TextConsole::new(); +/// This is our Operating System. It must be compiled separately. +/// +/// The RP2040 requires an OS linked at `0x1002_0000`, which is the OS binary +/// `flash1002`. Use `objdump` as per the README file to make a `flash1002.bin`. +#[link_section = ".flash_os"] +#[used] +pub static OS_IMAGE: [u8; include_bytes!("flash1002.bin").len()] = *include_bytes!("flash1002.bin"); + +/// The table of API calls we provide the OS +static API_CALLS: common::Api = common::Api { + api_version_get, + bios_version_get, + serial_configure, + serial_get_info, + serial_write, + time_get, + time_set, + configuration_get, + configuration_set, + video_is_valid_mode, + video_set_mode, + video_get_mode, + video_get_framebuffer, + video_set_framebuffer, + memory_get_region, +}; extern "C" { static mut _flash_os_start: u32; @@ -85,71 +111,43 @@ extern "C" { // Functions // ----------------------------------------------------------------------------- -/// Prints to the screen. -/// -/// This function is NOT interrupt safe. Only call from the main thread, on -/// core 1. -#[macro_export] -macro_rules! print { - ($($arg:tt)*) => { - { - use core::fmt::Write as _; - write!(&TEXT_CONSOLE, $($arg)*).unwrap(); - } - }; -} - -/// Prints to the screen and puts a new-line on the end. -/// -/// This function is NOT interrupt safe. Only call from the main thread, on -/// core 1. -#[macro_export] -macro_rules! println { - () => (print!("\n")); - ($($arg:tt)*) => { - { - use core::fmt::Write as _; - writeln!(&TEXT_CONSOLE, $($arg)*).unwrap(); - } - }; -} - /// This is the entry-point to the BIOS. It is called by cortex-m-rt once the /// `.bss` and `.data` sections have been initialised. #[entry] fn main() -> ! { cortex_m::interrupt::disable(); - info!("Neotron BIOS {} starting...", GIT_VERSION); + // BIOS_VERSION has a trailing `\0` as that is what the BIOS/OS API requires. + info!("{} starting...", &BIOS_VERSION[0..BIOS_VERSION.len() - 1]); // Grab the singleton containing all the RP2040 peripherals - let mut pac = pac::Peripherals::take().unwrap(); + let mut pp = pac::Peripherals::take().unwrap(); // Grab the singleton containing all the generic Cortex-M peripherals - let _core = pac::CorePeripherals::take().unwrap(); + let cp = pac::CorePeripherals::take().unwrap(); // Reset the DMA engine. If we don't do this, starting from probe-run // (as opposed to a cold-start) is unreliable. - pac.RESETS.reset.modify(|_r, w| w.dma().set_bit()); - cortex_m::asm::nop(); - pac.RESETS.reset.modify(|_r, w| w.dma().clear_bit()); - while pac.RESETS.reset_done.read().dma().bit_is_clear() {} + reset_dma_engine(&mut pp); // Needed by the clock setup - let mut watchdog = hal::watchdog::Watchdog::new(pac.WATCHDOG); + let mut watchdog = hal::watchdog::Watchdog::new(pp.WATCHDOG); - // Run at 126 MHz SYS_PLL, 48 MHz, USB_PLL + // Run at 126 MHz SYS_PLL, 48 MHz, USB_PLL. This is important, we as clock + // the PIO at ÷ 5, to give 25.2 MHz (which is close enough to the 25.175 + // MHz standard VGA pixel clock). - let xosc = hal::xosc::setup_xosc_blocking(pac.XOSC, rp_pico::XOSC_CRYSTAL_FREQ.Hz()) + // Step 1. Turn on the crystal. + let xosc = hal::xosc::setup_xosc_blocking(pp.XOSC, rp_pico::XOSC_CRYSTAL_FREQ.Hz()) .map_err(|_x| false) .unwrap(); - - // Configure watchdog tick generation to tick over every microsecond + // Step 2. Configure watchdog tick generation to tick over every microsecond. watchdog.enable_tick_generation((rp_pico::XOSC_CRYSTAL_FREQ / 1_000_000) as u8); - - let mut clocks = hal::clocks::ClocksManager::new(pac.CLOCKS); - + // Step 3. Create a clocks manager. + let mut clocks = hal::clocks::ClocksManager::new(pp.CLOCKS); + // Step 4. Set up the system PLL. We take Crystal Oscillator (=12 MHz), + // ×126 (=1512 MHz), ÷6 (=252 MHz), ÷2 (=126 MHz) let pll_sys = hal::pll::setup_pll_blocking( - pac.PLL_SYS, + pp.PLL_SYS, xosc.operating_frequency().into(), hal::pll::PLLConfig { vco_freq: Megahertz(1512), @@ -158,21 +156,21 @@ fn main() -> ! { post_div2: 2, }, &mut clocks, - &mut pac.RESETS, + &mut pp.RESETS, ) .map_err(|_x| false) .unwrap(); - + // Step 5. Set up a 48 MHz PLL for the USB system. let pll_usb = hal::pll::setup_pll_blocking( - pac.PLL_USB, + pp.PLL_USB, xosc.operating_frequency().into(), hal::pll::common_configs::PLL_USB_48MHZ, &mut clocks, - &mut pac.RESETS, + &mut pp.RESETS, ) .map_err(|_x| false) .unwrap(); - + // Step 6. Set the system to run from the PLLs we just configured. clocks .init_default(&xosc, &pll_sys, &pll_usb) .map_err(|_x| false) @@ -183,15 +181,10 @@ fn main() -> ! { // sio is the *Single-cycle Input/Output* peripheral. It has all our GPIO // pins, as well as some mailboxes and other useful things for inter-core // communications. - let mut sio = hal::sio::Sio::new(pac.SIO); + let mut sio = hal::sio::Sio::new(pp.SIO); // Configure and grab all the RP2040 pins the Pico exposes. - let pins = rp_pico::Pins::new( - pac.IO_BANK0, - pac.PADS_BANK0, - sio.gpio_bank0, - &mut pac.RESETS, - ); + let pins = rp_pico::Pins::new(pp.IO_BANK0, pp.PADS_BANK0, sio.gpio_bank0, &mut pp.RESETS); // Disable power save mode to force SMPS into low-efficiency, low-noise mode. let mut b_power_save = pins.b_power_save.into_push_pull_output(); @@ -216,58 +209,285 @@ fn main() -> ! { info!("Pins OK"); vga::init( - pac.PIO0, - pac.DMA, - &mut pac.RESETS, - &mut pac.PPB, + pp.PIO0, + pp.DMA, + &mut pp.RESETS, + &mut pp.PPB, &mut sio.fifo, - &mut pac.PSM, + &mut pp.PSM, ); - TEXT_CONSOLE.set_text_buffer(unsafe { &mut vga::GLYPH_ATTR_ARRAY }); + // Say hello over VGA (with a bit of a pause) + let mut delay = cortex_m::delay::Delay::new(cp.SYST, clocks.system_clock.freq().integer()); + sign_on(&mut delay); + + // Now jump to the OS + let flash_os_start = unsafe { &mut _flash_os_start as *mut u32 as usize }; + let code: &common::OsStartFn = unsafe { ::core::mem::transmute(flash_os_start) }; + code(&API_CALLS); +} + +fn sign_on(delay: &mut cortex_m::delay::Delay) { + static LICENCE_TEXT: &str = "\ + Copyright © Jonathan 'theJPster' Pallant and the Neotron Developers, 2021\n\ + \n\ + This program is free software: you can redistribute it and/or modify\n\ + it under the terms of the GNU General Public License as published by\n\ + the Free Software Foundation, either version 3 of the License, or\n\ + (at your option) any later version.\n\ + \n\ + This program is distributed in the hope that it will be useful,\n\ + but WITHOUT ANY WARRANTY; without even the implied warranty of\n\ + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n\ + GNU General Public License for more details.\n\ + \n\ + You should have received a copy of the GNU General Public License\n\ + along with this program. If not, see https://www.gnu.org/licenses/.\n"; + + // Create a new temporary console for some boot-up messages + let tc = vga::TextConsole::new(); + tc.set_text_buffer(unsafe { &mut vga::GLYPH_ATTR_ARRAY }); // A crude way to clear the screen for _col in 0..vga::NUM_TEXT_ROWS { - println!(); + writeln!(&tc).unwrap(); } - TEXT_CONSOLE.move_to(0, 0); - - println!("Neotron Pico BIOS, for the Raspberry Pi RP2040"); - println!("Copyright © Jonathan 'theJPster' Pallant and the Neotron Developers, 2021"); - println!("Version {}", GIT_VERSION); - println!(); - println!("This program is free software: you can redistribute it and/or modify"); - println!("it under the terms of the GNU General Public License as published by"); - println!("the Free Software Foundation, either version 3 of the License, or"); - println!("(at your option) any later version."); - println!(); - println!("This program is distributed in the hope that it will be useful,"); - println!("but WITHOUT ANY WARRANTY; without even the implied warranty of"); - println!("MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the"); - println!("GNU General Public License for more details."); - println!(); - println!("You should have received a copy of the GNU General Public License"); - println!("along with this program. If not, see https://www.gnu.org/licenses/."); - println!(); - println!("Searching for Neotron OS..."); + tc.move_to(0, 0); - let flash_os_start = unsafe { &mut _flash_os_start as *mut u32 as usize }; - let flash_os_len = unsafe { &mut _flash_os_len as *mut u32 as usize }; - let ram_os_start = unsafe { &mut _ram_os_start as *mut u32 as usize }; - let ram_os_len = unsafe { &mut _ram_os_len as *mut u32 as usize }; + writeln!(&tc, "{}", &BIOS_VERSION[0..BIOS_VERSION.len() - 1]).unwrap(); + write!(&tc, "{}", LICENCE_TEXT).unwrap(); - println!( - "OS Flash @ 0x{:08x}, {} bytes", - flash_os_start, flash_os_len - ); - println!("OS RAM @ 0x{:08x}, {} bytes", ram_os_start, ram_os_len); + writeln!(&tc, "Loading Neotron OS...").unwrap(); + + // Wait for a bit + for n in [5, 4, 3, 2, 1].iter() { + write!(&tc, "{}...", n).unwrap(); + delay.delay_ms(1000); + } + + // A crude way to clear the screen + for _col in 0..vga::NUM_TEXT_ROWS { + writeln!(&tc).unwrap(); + } + tc.move_to(0, 0); +} + +/// Reset the DMA Peripheral. +fn reset_dma_engine(pp: &mut pac::Peripherals) { + pp.RESETS.reset.modify(|_r, w| w.dma().set_bit()); + cortex_m::asm::nop(); + pp.RESETS.reset.modify(|_r, w| w.dma().clear_bit()); + while pp.RESETS.reset_done.read().dma().bit_is_clear() {} +} + +/// Returns the version number of the BIOS API. +pub extern "C" fn api_version_get() -> common::Version { + common::API_VERSION +} + +/// Returns a pointer to a static string slice containing the BIOS Version. +/// +/// This string contains the version number and build string of the BIOS. +/// For C compatibility this string is null-terminated and guaranteed to +/// only contain ASCII characters (bytes with a value 127 or lower). We +/// also pass the length (excluding the null) to make it easy to construct +/// a Rust string. It is unspecified as to whether the string is located +/// in Flash ROM or RAM (but it's likely to be Flash ROM). +pub extern "C" fn bios_version_get() -> common::ApiString<'static> { + common::ApiString::new(BIOS_VERSION) +} + +/// Get information about the Serial ports in the system. +/// +/// Serial ports are ordered octet-oriented pipes. You can push octets +/// into them using a 'write' call, and pull bytes out of them using a +/// 'read' call. They have options which allow them to be configured at +/// different speeds, or with different transmission settings (parity +/// bits, stop bits, etc) - you set these with a call to +/// `SerialConfigure`. They may physically be a MIDI interface, an RS-232 +/// port or a USB-Serial port. There is no sense of 'open' or 'close' - +/// that is an Operating System level design feature. These APIs just +/// reflect the raw hardware, in a similar manner to the registers exposed +/// by a memory-mapped UART peripheral. +pub extern "C" fn serial_get_info(_device: u8) -> common::Option { + common::Option::None +} + +/// Set the options for a given serial device. An error is returned if the +/// options are invalid for that serial device. +pub extern "C" fn serial_configure( + _device: u8, + _config: common::serial::Config, +) -> common::Result<()> { + common::Result::Err(common::Error::Unimplemented) +} - // A dummy loop so we know it's running - let mut x: u32 = 0; - loop { - print!("\rx = {}", x); - x = x.wrapping_add(1); +/// Write bytes to a serial port. There is no sense of 'opening' or +/// 'closing' the device - serial devices are always open. If the return +/// value is `Ok(n)`, the value `n` may be less than the size of the given +/// buffer. If so, that means not all of the data could be transmitted - +/// only the first `n` bytes were. +pub extern "C" fn serial_write( + _device: u8, + _data: common::ApiByteSlice, + _timeout: common::Option, +) -> common::Result { + common::Result::Err(common::Error::Unimplemented) +} + +/// Get the current wall time. +/// +/// The Neotron BIOS does not understand time zones, leap-seconds or the +/// Gregorian calendar. It simply stores time as an incrementing number of +/// seconds since some epoch, and the number of milliseconds since that second +/// began. A day is assumed to be exactly 86,400 seconds long. This is a lot +/// like POSIX time, except we have a different epoch +/// - the Neotron epoch is 2000-01-01T00:00:00Z. It is highly recommend that you +/// store UTC in the BIOS and use the OS to handle time-zones. +/// +/// If the BIOS does not have a battery-backed clock, or if that battery has +/// failed to keep time, the system starts up assuming it is the epoch. +pub extern "C" fn time_get() -> common::Time { + // TODO: Read from the MCP7940N + common::Time { secs: 0, nsecs: 0 } +} + +/// Set the current wall time. +/// +/// See `time_get` for a description of now the Neotron BIOS should handle +/// time. +/// +/// You only need to call this whenever you get a new sense of the current +/// time (e.g. the user has updated the current time, or if you get a GPS +/// fix). The BIOS should push the time out to the battery-backed Real +/// Time Clock, if it has one. +pub extern "C" fn time_set(_time: common::Time) { + // TODO: Update the MCP7940N RTC +} + +/// Get the configuration data block. +/// +/// Configuration data is, to the BIOS, just a block of bytes of a given +/// length. How it stores them is up to the BIOS - it could be EEPROM, or +/// battery-backed SRAM. +pub extern "C" fn configuration_get(_buffer: common::ApiBuffer) -> common::Result { + common::Result::Err(common::Error::Unimplemented) +} + +/// Set the configuration data block. +/// +/// See `configuration_get`. +pub extern "C" fn configuration_set(_buffer: common::ApiByteSlice) -> common::Result<()> { + common::Result::Err(common::Error::Unimplemented) +} + +/// Does this Neotron BIOS support this video mode? +pub extern "C" fn video_is_valid_mode(mode: common::video::Mode) -> bool { + mode == common::video::Mode::new( + common::video::Timing::T640x480, + common::video::Format::Text8x16, + ) +} + +/// Switch to a new video mode. +/// +/// The contents of the screen are undefined after a call to this function. +/// +/// If the BIOS does not have enough reserved RAM (or dedicated VRAM) to +/// support this mode, the change will succeed but a subsequent call to +/// `video_get_framebuffer` will return `null`. You must then supply a +/// pointer to a block of size `Mode::frame_size_bytes()` to +/// `video_set_framebuffer` before any video will appear. +pub extern "C" fn video_set_mode(_mode: common::video::Mode) -> common::Result<()> { + common::Result::Err(common::Error::Unimplemented) +} + +/// Returns the video mode the BIOS is currently in. +/// +/// The OS should call this function immediately after start-up and note +/// the value - this is the `default` video mode which can always be +/// serviced without supplying extra RAM. +pub extern "C" fn video_get_mode() -> common::video::Mode { + common::video::Mode::new( + common::video::Timing::T640x480, + common::video::Format::Text8x16, + ) +} + +/// Get the framebuffer address. +/// +/// We can write through this address to the video framebuffer. The +/// meaning of the data we write, and the size of the region we are +/// allowed to write to, is a function of the current video mode (see +/// `video_get_mode`). +/// +/// This function will return `null` if the BIOS isn't able to support the +/// current video mode from its memory reserves. If that happens, you will +/// need to use some OS RAM or Application RAM and provide that as a +/// framebuffer to `video_set_framebuffer`. The BIOS will always be able +/// to provide the 'basic' text buffer experience from reserves, so this +/// function will never return `null` on start-up. +pub extern "C" fn video_get_framebuffer() -> *mut u8 { + unsafe { vga::GLYPH_ATTR_ARRAY.as_mut_ptr() as *mut u8 } +} + +/// Set the framebuffer address. +/// +/// Tell the BIOS where it should start fetching pixel or textual data +/// from (depending on the current video mode). +/// +/// This value is forgotten after a video mode change and must be +/// re-supplied. +pub extern "C" fn video_set_framebuffer(_buffer: *mut u8) -> common::Result<()> { + common::Result::Err(common::Error::Unimplemented) +} + +/// Find out how large a given region of memory is. +/// +/// The first region is the 'main application region' and is defined to always +/// start at address `0x2000_0000` on a standard Cortex-M system. This +/// application region stops just before the BIOS reserved memory, at the top of +/// the internal SRAM. The OS will have been linked to use the first 1 KiB of +/// this region. +/// +/// Other regions may be located at other addresses (e.g. external DRAM or +/// PSRAM). +/// +/// The OS will always load non-relocatable applications into the bottom of +/// Region 0. It can allocate OS specific structures from any other Region (if +/// any), or from the top of Region 0 (although this reduces the maximum +/// application space available). The OS will prefer lower numbered regions +/// (other than Region 0), so faster memory should be listed first. +/// +/// If the region number given is invalid, the function returns `(null, 0)`. +/// +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub extern "C" fn memory_get_region( + region: u8, + out_start: *mut *mut u8, + out_len: *mut usize, +) -> common::Result<()> { + // The clippy allow is because the API isn't marked as 'unsafe', and so we + // can't mark this function as 'unsafe'. + match region { + // Application Region + 0 => { + if !out_start.is_null() { + unsafe { + let ram_os_start = &mut _ram_os_start as *mut u32 as *mut u8; + out_start.write(ram_os_start); + } + } + if !out_len.is_null() { + unsafe { + let ram_os_len = &mut _ram_os_len as *const u32 as usize; + out_len.write(ram_os_len); + } + } + common::Result::Ok(()) + } + _ => common::Result::Err(common::Error::InvalidDevice), } } From 55c7b6058a081aff028321f7b1b189af13b1f880 Mon Sep 17 00:00:00 2001 From: "Jonathan Pallant (Ferrous Systems)" Date: Fri, 18 Mar 2022 19:39:35 +0000 Subject: [PATCH 41/41] Update to 0.3.0 --- CHANGELOG.md | 8 +++++++- Cargo.toml | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f982a89..cfd2690 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,15 @@ # Changelog -## Unreleased Changes ([Source](https://github.com/neotron-compute/neotron-pico-bios/tree/master) | [Changes](https://github.com/neotron-compute/neotron-pico-bios/compare/v0.1.0...master)) +## Unreleased Changes ([Source](https://github.com/neotron-compute/neotron-pico-bios/tree/develop) | [Changes](https://github.com/neotron-compute/neotron-pico-bios/compare/v0.3.0...develop)) * None +## v0.3.0 ([Source](https://github.com/neotron-compute/neotron-pico-bios/tree/v0.3.0) | [Release](https://github.com/neotron-compute/neotron-pico-bios/release/tag/v0.3.0)) + +* Boots OS 0.1.0 +* VGA 80x30 text mode (640x480 @ 60Hz) +* Update defmt and other crates + ## v0.2.0 ([Source](https://github.com/neotron-compute/neotron-pico-bios/tree/v0.2.0) | [Release](https://github.com/neotron-compute/neotron-pico-bios/release/tag/v0.2.0)) * Add RTT debugging using defmt diff --git a/Cargo.toml b/Cargo.toml index 6b7cbd3..0e2a8e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ resolver = "2" readme = "README.md" license = "GPL-3.0-or-later" name = "neotron-pico-bios" -version = "0.2.0" +version = "0.3.0" [dependencies] # Useful Cortex-M specific functions (e.g. SysTick)