#![no_std]
#![feature(const_fn)]
extern crate console_traits;
mod charset;
mod font8x16;
mod font8x8;
pub use charset::*;
pub use console_traits::*;
use font8x16::Font8x16;
use core::sync::atomic::{AtomicUsize, Ordering};
pub trait Font {
fn pixels(&self, glyph: Char, row: usize) -> u8;
fn height_pixels(&self) -> usize;
fn width_pixels(&self) -> usize;
fn length_bytes(&self) -> usize;
}
const H_VISIBLE_AREA: u32 = 400;
const H_FRONT_PORCH: u32 = 20;
const H_SYNC_PULSE: u32 = 64;
const H_BACK_PORCH: u32 = 44;
const H_WHOLE_LINE: u32 = H_VISIBLE_AREA + H_FRONT_PORCH + H_SYNC_PULSE + H_BACK_PORCH;
const V_VISIBLE_AREA: usize = 600;
const V_FRONT_PORCH: usize = 1;
const V_SYNC_PULSE: usize = 4;
const V_BACK_PORCH: usize = 23;
const V_WHOLE_FRAME: usize = V_SYNC_PULSE + V_BACK_PORCH + V_VISIBLE_AREA + V_FRONT_PORCH;
const V_TOP_BORDER: usize = 12;
const V_BOTTOM_BORDER: usize = 12;
const MAX_FONT_HEIGHT: usize = 16;
const MAX_FONT_WIDTH: usize = 8;
const V_SYNC_PULSE_FIRST: usize = 0;
const V_BACK_PORCH_FIRST: usize = V_SYNC_PULSE_FIRST + V_SYNC_PULSE;
const V_TOP_BORDER_FIRST: usize = V_BACK_PORCH_FIRST + V_BACK_PORCH;
const V_TOP_BORDER_LAST: usize = V_DATA_FIRST - 1;
const V_DATA_FIRST: usize = V_TOP_BORDER_FIRST + V_TOP_BORDER;
const V_DATA_LAST: usize = V_BOTTOM_BORDER_FIRST - 1;
const V_BOTTOM_BORDER_FIRST: usize = V_DATA_FIRST + (MAX_FONT_HEIGHT * TEXT_NUM_ROWS);
const V_BOTTOM_BORDER_LAST: usize = V_FRONT_PORCH_FIRST - 1;
const V_FRONT_PORCH_FIRST: usize = V_BOTTOM_BORDER_FIRST + V_BOTTOM_BORDER;
const PIXEL_CLOCK: u32 = 20_000_000;
pub const TOP_BOTTOM_BORDER_HEIGHT: usize = 12;
pub const LEFT_RIGHT_BORDER_WIDTH: usize = 8;
pub const USABLE_LINES: usize = 576;
pub const USABLE_LINES_MODE2: usize = 288;
pub const USABLE_COLS: usize = 384;
pub const MAX_Y: usize = USABLE_LINES - 1;
pub const MAX_X: usize = USABLE_COLS - 1;
pub const HORIZONTAL_OCTETS: usize = 50;
pub const USABLE_HORIZONTAL_OCTETS: usize = 48;
pub const TEXT_NUM_COLS: usize = USABLE_COLS / MAX_FONT_WIDTH;
pub const TEXT_MAX_COL: usize = TEXT_NUM_COLS - 1;
pub const TEXT_NUM_ROWS: usize = USABLE_LINES / MAX_FONT_HEIGHT;
pub const TEXT_MAX_ROW: usize = TEXT_NUM_ROWS - 1;
pub trait Hardware {
fn configure(&mut self, width: u32, sync_end: u32, line_start: u32, clock_rate: u32);
fn vsync_on(&mut self);
fn vsync_off(&mut self);
fn write_pixels(&mut self, red: u32, green: u32, blue: u32);
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub struct Point(pub usize, pub usize);
#[derive(Copy, Clone)]
pub struct TextRow {
pub glyphs: [(Char, Attr); TEXT_NUM_COLS],
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub struct Attr(u8);
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum Colour {
White = 7,
Yellow = 6,
Magenta = 5,
Red = 4,
Cyan = 3,
Green = 2,
Blue = 1,
Black = 0,
}
impl Attr {
pub const fn new(fg: Colour, bg: Colour) -> Attr {
Attr(((fg as u8) * 8) + (bg as u8))
}
pub fn set_fg(&mut self, fg: Colour) -> &mut Attr {
self.0 = ((fg as u8) * 8) + (self.0 & 0x7);
self
}
pub fn set_bg(&mut self, bg: Colour) -> &mut Attr {
self.0 = (self.0 & 0x38) + (bg as u8);
self
}
}
const DEFAULT_ATTR: Attr = Attr((7 * 8) + 1);
impl core::default::Default for Attr {
fn default() -> Self {
DEFAULT_ATTR
}
}
const RGB_MAPS: [[u32; 256]; 64] = include!("maps.txt");
pub struct Mode2<'a> {
buffer: &'a mut [u8],
start: usize,
end: usize,
}
pub struct FrameBuffer<'a, T>
where
T: Hardware,
{
line_no: AtomicUsize,
frame: usize,
text_buffer: [TextRow; TEXT_NUM_ROWS],
hw: Option<T>,
attr: Attr,
pos: Position,
mode: ControlCharMode,
escape_mode: EscapeCharMode,
mode2: Option<Mode2<'a>>,
}
impl<'a, T> FrameBuffer<'a, T>
where
T: Hardware,
{
pub const fn new() -> FrameBuffer<'a, T> {
FrameBuffer {
line_no: AtomicUsize::new(0),
frame: 0,
text_buffer: [TextRow {
glyphs: [(Char::Null, DEFAULT_ATTR); TEXT_NUM_COLS],
}; TEXT_NUM_ROWS],
hw: None,
pos: Position {
row: Row(0),
col: Col(0),
},
attr: DEFAULT_ATTR,
mode: ControlCharMode::Interpret,
escape_mode: EscapeCharMode::Waiting,
mode2: None,
}
}
pub fn init(&mut self, mut hw: T) {
assert_eq!(MAX_FONT_WIDTH, 8);
hw.configure(
H_WHOLE_LINE,
H_SYNC_PULSE,
H_SYNC_PULSE + H_BACK_PORCH,
PIXEL_CLOCK,
);
self.hw = Some(hw);
self.clear();
}
pub fn mode2(&mut self, buffer: &'a mut [u8], start_line: usize) {
let length = buffer.len();
let buffer_lines = length / USABLE_HORIZONTAL_OCTETS;
let mode2 = Mode2 {
buffer: buffer,
start: start_line,
end: start_line + (2 * buffer_lines),
};
self.mode2 = Some(mode2);
}
pub fn mode2_shift(&mut self, new_start_line: usize) {
if let Some(mode2) = self.mode2.as_mut() {
mode2.start = new_start_line;
}
}
pub fn mode2_release(&mut self) -> Option<(&'a mut [u8], usize)> {
let mut mode2_opt = None;
core::mem::swap(&mut self.mode2, &mut mode2_opt);
if let Some(mode2) = mode2_opt {
Some((mode2.buffer, mode2.start))
} else {
None
}
}
pub fn frame(&self) -> usize {
self.frame
}
pub fn line(&self) -> Option<usize> {
let line = self.line_no.load(Ordering::Relaxed);
if line >= V_DATA_FIRST && line <= V_DATA_LAST {
Some(line - V_DATA_FIRST)
} else {
None
}
}
pub fn total_line(&self) -> u64 {
let line_a = self.line_no.load(Ordering::Relaxed);
let mut f = self.frame;
let line_b = self.line_no.load(Ordering::Relaxed);
if line_b < line_a {
f = self.frame;
}
((f as u64) * (V_WHOLE_FRAME as u64)) + (line_b as u64)
}
pub fn isr_sol(&mut self) {
let current_line = self.line_no.fetch_add(1, Ordering::Relaxed);
match current_line {
V_BACK_PORCH_FIRST => {
if let Some(ref mut hw) = self.hw {
hw.vsync_off();
}
}
V_TOP_BORDER_FIRST...V_TOP_BORDER_LAST => {
self.solid_line();
}
V_DATA_FIRST...V_DATA_LAST => {
let line = current_line - V_DATA_FIRST;
self.calculate_pixels(line);
}
V_BOTTOM_BORDER_FIRST...V_BOTTOM_BORDER_LAST => {
self.solid_line();
}
V_FRONT_PORCH_FIRST => {
self.frame = self.frame.wrapping_add(1);
}
V_WHOLE_FRAME => {
self.line_no.store(0, Ordering::Relaxed);
if let Some(ref mut hw) = self.hw {
hw.vsync_on();
}
}
_ => {
}
}
}
fn solid_line(&mut self) {
if let Some(ref mut hw) = self.hw {
for _ in 0..HORIZONTAL_OCTETS {
hw.write_pixels(0xFF, 0xFF, 0xFF);
}
}
}
fn calculate_pixels(&mut self, line: usize) {
let text_row = line / MAX_FONT_HEIGHT;
let font_row = line % MAX_FONT_HEIGHT;
let font = Font8x16;
if let Some(ref mut hw) = self.hw {
hw.write_pixels(0xFF, 0xFF, 0xFF);
let mut need_text = true;
if let Some(mode2) = self.mode2.as_ref() {
if line >= mode2.start && line < mode2.end && text_row < TEXT_NUM_ROWS {
let framebuffer_line = (line - mode2.start) >> 1;
let start = framebuffer_line * USABLE_HORIZONTAL_OCTETS;
let framebuffer_bytes =
&mode2.buffer[start..(start + USABLE_HORIZONTAL_OCTETS)];
for ((_, attr), bitmap) in self.text_buffer[text_row]
.glyphs
.iter()
.zip(framebuffer_bytes.iter())
{
let w = *bitmap;
let rgb_addr = (RGB_MAPS.as_ptr() as usize)
+ (attr.0 as usize * 1024_usize)
+ (w as usize * 4_usize);
let rgb_word = unsafe { core::ptr::read(rgb_addr as *const u32) };
hw.write_pixels(rgb_word >> 16, rgb_word >> 8, rgb_word);
}
need_text = false;
}
}
if need_text {
if text_row < TEXT_NUM_ROWS {
for (ch, attr) in self.text_buffer[text_row].glyphs.iter() {
let w = font.pixels(*ch, font_row);
let rgb_addr = (RGB_MAPS.as_ptr() as usize)
+ (attr.0 as usize * 1024_usize)
+ (w as usize * 4_usize);
let rgb_word = unsafe { core::ptr::read(rgb_addr as *const u32) };
hw.write_pixels(rgb_word >> 16, rgb_word >> 8, rgb_word);
}
}
}
hw.write_pixels(0xFF, 0xFF, 0xFF);
}
}
pub fn clear(&mut self) {
for row in self.text_buffer.iter_mut() {
for slot in row.glyphs.iter_mut() {
*slot = (Char::Space, self.attr);
}
}
self.pos = Position::origin();
}
pub fn write_glyph_at(&mut self, glyph: Char, pos: Position, attr: Option<Attr>) {
if (pos.col <= self.get_width()) && (pos.row <= self.get_height()) {
self.text_buffer[pos.row.0 as usize].glyphs[pos.col.0 as usize] =
(glyph, attr.unwrap_or(self.attr));
}
}
pub fn write_glyph(&mut self, glyph: Char, attr: Option<Attr>) {
self.text_buffer[self.pos.row.0 as usize].glyphs[self.pos.col.0 as usize] =
(glyph, attr.unwrap_or(self.attr));
self.move_cursor_right().unwrap();
}
pub fn set_attr_at(&mut self, pos: Position, attr: Attr) {
self.text_buffer[pos.row.0 as usize].glyphs[pos.col.0 as usize].1 = attr;
}
pub fn set_attr(&mut self, attr: Attr) -> Attr {
let old = self.attr;
self.attr = attr;
old
}
pub fn get_attr(&mut self) -> Attr {
self.attr
}
}
impl<'a, T> BaseConsole for FrameBuffer<'a, T>
where
T: Hardware,
{
type Error = ();
fn get_width(&self) -> Col {
Col(TEXT_MAX_COL as u8)
}
fn get_height(&self) -> Row {
Row(TEXT_MAX_ROW as u8)
}
fn set_col(&mut self, col: Col) -> Result<(), Self::Error> {
self.pos.col = col;
Ok(())
}
fn set_row(&mut self, row: Row) -> Result<(), Self::Error> {
self.pos.row = row;
Ok(())
}
fn set_pos(&mut self, pos: Position) -> Result<(), Self::Error> {
self.pos = pos;
Ok(())
}
fn get_pos(&self) -> Position {
self.pos
}
fn set_control_char_mode(&mut self, mode: ControlCharMode) {
self.mode = mode;
}
fn get_control_char_mode(&self) -> ControlCharMode {
self.mode
}
fn set_escape_char_mode(&mut self, mode: EscapeCharMode) {
self.escape_mode = mode;
}
fn get_escape_char_mode(&self) -> EscapeCharMode {
self.escape_mode
}
fn scroll_screen(&mut self) -> Result<(), Self::Error> {
for line in 0..TEXT_NUM_ROWS - 1 {
self.text_buffer[line] = self.text_buffer[line + 1];
}
for slot in self.text_buffer[TEXT_MAX_ROW].glyphs.iter_mut() {
*slot = (Char::Space, self.attr);
}
Ok(())
}
}
impl<'a, T> AsciiConsole for FrameBuffer<'a, T>
where
T: Hardware,
{
fn handle_escape(&mut self, escaped_char: u8) -> bool {
match escaped_char {
b'W' => {
self.attr.set_fg(Colour::White);
}
b'Y' => {
self.attr.set_fg(Colour::Yellow);
}
b'M' => {
self.attr.set_fg(Colour::Magenta);
}
b'R' => {
self.attr.set_fg(Colour::Red);
}
b'C' => {
self.attr.set_fg(Colour::Cyan);
}
b'G' => {
self.attr.set_fg(Colour::Green);
}
b'B' => {
self.attr.set_fg(Colour::Blue);
}
b'K' => {
self.attr.set_fg(Colour::Black);
}
b'w' => {
self.attr.set_bg(Colour::White);
}
b'y' => {
self.attr.set_bg(Colour::Yellow);
}
b'm' => {
self.attr.set_bg(Colour::Magenta);
}
b'r' => {
self.attr.set_bg(Colour::Red);
}
b'c' => {
self.attr.set_bg(Colour::Cyan);
}
b'g' => {
self.attr.set_bg(Colour::Green);
}
b'b' => {
self.attr.set_bg(Colour::Blue);
}
b'k' => {
self.attr.set_bg(Colour::Black);
}
b'Z' => {
self.clear();
}
_ => {}
}
true
}
fn write_char_at(&mut self, ch: u8, pos: Position) -> Result<(), Self::Error> {
if (pos.col <= self.get_width()) && (pos.row <= self.get_height()) {
self.text_buffer[pos.row.0 as usize].glyphs[pos.col.0 as usize] =
(Char::from_byte(ch), self.attr);
}
Ok(())
}
}
impl<'a, T> core::fmt::Write for FrameBuffer<'a, T>
where
T: Hardware,
{
fn write_str(&mut self, s: &str) -> core::fmt::Result {
for ch in s.chars() {
self.write_character(Char::map_char(ch) as u8).map_err(|_| core::fmt::Error)?;
}
Ok(())
}
}