stuff
This commit is contained in:
@@ -48,6 +48,7 @@ target_link_libraries(picopal
|
||||
hardware_pio
|
||||
hardware_timer
|
||||
hardware_clocks
|
||||
pico_atomic
|
||||
pico_multicore
|
||||
)
|
||||
|
||||
|
||||
255
core1.c
255
core1.c
@@ -1,12 +1,17 @@
|
||||
#include "core1.h"
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdatomic.h>
|
||||
|
||||
#include "pico/stdlib.h"
|
||||
#include "pico/multicore.h"
|
||||
#include "hardware/irq.h"
|
||||
#include "hardware/pio.h"
|
||||
|
||||
#include "core_comm.h"
|
||||
#include "z80_bus.pio.h"
|
||||
#include "text_mode.h"
|
||||
#include "cvideo.h"
|
||||
|
||||
#define Z80_PIO pio1
|
||||
#define Z80_MONITOR_SM 0
|
||||
@@ -20,25 +25,102 @@
|
||||
#define Z80_CS_PIN 12
|
||||
#define Z80_A0_PIN 13
|
||||
#define Z80_A1_PIN 14
|
||||
#define Z80_CLK_PIN 15
|
||||
#define Z80_WAIT_PIN 16
|
||||
|
||||
#define Z80_RD_MASK (1u << Z80_RD_PIN)
|
||||
#define Z80_CS_MASK (1u << Z80_CS_PIN)
|
||||
#define Z80_ADDR_MASK 0x3u
|
||||
#define Z80_DATA_MASK 0xffu
|
||||
#define Z80_DIRTY_SHIFT 32u
|
||||
#define Z80_DIRTY_MASK (UINT64_C(0x0f) << Z80_DIRTY_SHIFT)
|
||||
|
||||
// Bits 0..31 contain registers 0..3. Bits 32..35 are their dirty flags.
|
||||
static _Atomic uint64_t z80_registers;
|
||||
|
||||
uint16_t cursor_x = 0;
|
||||
uint16_t cursor_y = 0;
|
||||
uint16_t pixel_x = 0;
|
||||
uint16_t pixel_y = 0;
|
||||
|
||||
#define VID_CMD_SET_TEXT_MODE 0
|
||||
#define VID_CMD_SET_PIXEL_MODE 1
|
||||
#define VID_CMD_CLR 2
|
||||
#define VID_CMD_TEXT_POS_X 3
|
||||
#define VID_CMD_PIXEL_POS_X 4
|
||||
#define VID_CMD_TEXT_POS_Y 5
|
||||
#define VID_CMD_PIXEL_POS_Y 6
|
||||
|
||||
|
||||
typedef enum VID_MODE{
|
||||
VID_MODE_TEXT,
|
||||
VID_MODE_PIXEL,
|
||||
VID_MODE_TEXT_POS_X,
|
||||
VID_MODE_TEXT_POS_Y,
|
||||
VID_MODE_PIXEL_POS_X_1,
|
||||
VID_MODE_PIXEL_POS_X_2,
|
||||
VID_MODE_PIXEL_POS_Y_1,
|
||||
VID_MODE_PIXEL_POS_Y_2,
|
||||
} vid_mode_t;
|
||||
vid_mode_t vid_mode = VID_MODE_TEXT;
|
||||
|
||||
|
||||
// --------------------------------
|
||||
// CORE 1
|
||||
// --------------------------------
|
||||
|
||||
static void core1_puts(const char *s) {
|
||||
static void core1_putc(char c){
|
||||
video_command_t cmd;
|
||||
|
||||
switch((uint8_t)c){
|
||||
case '\r':
|
||||
cursor_x = 0;
|
||||
break;
|
||||
case '\n':
|
||||
cursor_y = (cursor_y<CHAR_LINES-1) ? cursor_y+1 : cursor_y;
|
||||
break;
|
||||
default:
|
||||
cmd.cmd = VIDEO_WRITE_TEXT,
|
||||
cmd.data[0] = (uint16_t)c,
|
||||
cmd.data[1] = cursor_x,
|
||||
cmd.data[2] = cursor_y,
|
||||
core1_to_core0_write_blocking(*((uint64_t*)&cmd));
|
||||
cursor_x = (cursor_x<CHARS_PER_LINE-1) ? cursor_x+1 : cursor_x;
|
||||
}
|
||||
}
|
||||
|
||||
static void core1_puts(char * s){
|
||||
while(*s){
|
||||
core1_to_core0_write_blocking((uint32_t)*s);
|
||||
core1_putc(*s);
|
||||
s++;
|
||||
}
|
||||
}
|
||||
|
||||
static void z80_write_monitor_init(void) {
|
||||
uint offset = pio_add_program(Z80_PIO, &z80_write_monitor_program);
|
||||
static void z80_bus_irq_handler(void) {
|
||||
while (!pio_sm_is_rx_fifo_empty(Z80_PIO, Z80_MONITOR_SM)) {
|
||||
uint32_t bus_sample = pio_sm_get(Z80_PIO, Z80_MONITOR_SM);
|
||||
uint8_t addr = (bus_sample >> Z80_A0_PIN) & Z80_ADDR_MASK;
|
||||
uint32_t shift = (uint32_t)addr * 8u;
|
||||
|
||||
uint64_t registers = atomic_load_explicit(&z80_registers,
|
||||
memory_order_relaxed);
|
||||
|
||||
if ((bus_sample & Z80_RD_MASK) != 0) {
|
||||
uint8_t data = (bus_sample >> Z80_DATA_PIN) & Z80_DATA_MASK;
|
||||
registers &= ~(UINT64_C(0xff) << shift);
|
||||
registers |= (uint64_t)data << shift;
|
||||
registers |= UINT64_C(1) << (Z80_DIRTY_SHIFT + addr);
|
||||
atomic_store_explicit(&z80_registers, registers,
|
||||
memory_order_release);
|
||||
} else {
|
||||
uint8_t data = (registers >> shift) & Z80_DATA_MASK;
|
||||
pio_sm_put(Z80_PIO, Z80_MONITOR_SM, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void z80_bus_init(void) {
|
||||
uint offset = pio_add_program(Z80_PIO, &z80_bus_program);
|
||||
|
||||
// Keep the bus isolated while selecting B (Z80) -> A (Pico).
|
||||
gpio_init(Z80_OE_PIN);
|
||||
@@ -47,60 +129,149 @@ static void z80_write_monitor_init(void) {
|
||||
gpio_init(Z80_DIR_PIN);
|
||||
gpio_put(Z80_DIR_PIN, 0);
|
||||
gpio_set_dir(Z80_DIR_PIN, GPIO_OUT);
|
||||
gpio_init(Z80_WAIT_PIN);
|
||||
gpio_put(Z80_WAIT_PIN, 1);
|
||||
gpio_set_dir(Z80_WAIT_PIN, GPIO_OUT);
|
||||
|
||||
for (uint pin = Z80_DATA_PIN; pin <= Z80_A1_PIN; pin++) {
|
||||
for (uint pin = Z80_OE_PIN; pin <= Z80_CLK_PIN; pin++) {
|
||||
pio_gpio_init(Z80_PIO, pin);
|
||||
}
|
||||
pio_sm_set_consecutive_pindirs(Z80_PIO, Z80_MONITOR_SM,
|
||||
Z80_DATA_PIN,
|
||||
Z80_A1_PIN - Z80_DATA_PIN + 1,
|
||||
false);
|
||||
pio_gpio_init(Z80_PIO, Z80_WAIT_PIN);
|
||||
|
||||
pio_sm_config config = z80_write_monitor_program_get_default_config(offset);
|
||||
pio_sm_config config = z80_bus_program_get_default_config(offset);
|
||||
sm_config_set_in_pins(&config, Z80_OE_PIN);
|
||||
sm_config_set_in_shift(&config, false, false, 32);
|
||||
sm_config_set_out_pins(&config, Z80_DATA_PIN, Z80_DATA_WIDTH);
|
||||
sm_config_set_out_shift(&config, true, false, 32);
|
||||
sm_config_set_set_pins(&config, Z80_WAIT_PIN, 1);
|
||||
sm_config_set_sideset_pins(&config, Z80_OE_PIN);
|
||||
pio_sm_init(Z80_PIO, Z80_MONITOR_SM, offset, &config);
|
||||
pio_sm_set_enabled(Z80_PIO, Z80_MONITOR_SM, true);
|
||||
|
||||
// Enable only after every Pico-side data pin has become an input.
|
||||
gpio_put(Z80_OE_PIN, 0);
|
||||
uint32_t control_mask = (1u << Z80_OE_PIN) |
|
||||
(1u << Z80_DIR_PIN) |
|
||||
(1u << Z80_WAIT_PIN);
|
||||
uint32_t initial_levels = (1u << Z80_WAIT_PIN);
|
||||
pio_sm_set_pins_with_mask(Z80_PIO, Z80_MONITOR_SM,
|
||||
initial_levels, control_mask);
|
||||
pio_sm_set_pindirs_with_mask(Z80_PIO, Z80_MONITOR_SM,
|
||||
control_mask, control_mask);
|
||||
pio_sm_set_consecutive_pindirs(Z80_PIO, Z80_MONITOR_SM,
|
||||
Z80_DATA_PIN,
|
||||
Z80_CLK_PIN - Z80_DATA_PIN + 1,
|
||||
false);
|
||||
|
||||
// IRQ configuration is per-core. This function runs on core 1, so bus
|
||||
// events preempt diagnostics on core 1 and never involve core 0.
|
||||
pio_set_irq0_source_enabled(Z80_PIO,
|
||||
pis_sm0_rx_fifo_not_empty,
|
||||
true);
|
||||
irq_set_exclusive_handler(PIO1_IRQ_0, z80_bus_irq_handler);
|
||||
irq_set_priority(PIO1_IRQ_0, PICO_HIGHEST_IRQ_PRIORITY);
|
||||
irq_set_enabled(PIO1_IRQ_0, true);
|
||||
|
||||
pio_sm_set_enabled(Z80_PIO, Z80_MONITOR_SM, true);
|
||||
}
|
||||
|
||||
void core1_entry() {
|
||||
z80_write_monitor_init();
|
||||
core1_puts("core 1: Z80 write monitor started\r\n");
|
||||
|
||||
uint64_t write_count = 0;
|
||||
uint8_t last_addr = 0;
|
||||
uint8_t last_data = 0;
|
||||
absolute_time_t next_report = make_timeout_time_ms(100);
|
||||
z80_bus_init();
|
||||
core1_puts("core 1: Z80 read/write bus started > ");
|
||||
|
||||
while (true) {
|
||||
while (!pio_sm_is_rx_fifo_empty(Z80_PIO, Z80_MONITOR_SM)) {
|
||||
uint32_t bus_sample = pio_sm_get(Z80_PIO, Z80_MONITOR_SM);
|
||||
// Check if it is for us -> CS=1
|
||||
if ((bus_sample & Z80_CS_MASK) != 0) {
|
||||
uint8_t addr = (bus_sample >> Z80_A0_PIN) & Z80_ADDR_MASK;
|
||||
if ((bus_sample & Z80_RD_MASK) != 0) {
|
||||
// WR
|
||||
last_addr = addr;
|
||||
last_data = (bus_sample >> Z80_DATA_PIN) & Z80_DATA_MASK;
|
||||
write_count += 1;
|
||||
} else {
|
||||
// RD
|
||||
}
|
||||
// Take a coherent snapshot and consume all four dirty flags. A
|
||||
// concurrent later write sets its register's flag again.
|
||||
uint64_t registers_i = atomic_fetch_and_explicit(&z80_registers, ~Z80_DIRTY_MASK, memory_order_acq_rel);
|
||||
registers_t regs = *((registers_t*)®isters_i);
|
||||
if((regs.flags & 0x01) == 0x01){
|
||||
// Write to vid_cmd happened
|
||||
switch(regs.vid_cmd){
|
||||
case VID_CMD_SET_TEXT_MODE: {
|
||||
video_command_t cmd = {
|
||||
.cmd = VIDEO_COMMAND_MODE_SET,
|
||||
.data[0] = VIDEO_MODE_TEXT,
|
||||
};
|
||||
core1_to_core0_write_blocking(*((uint64_t*)&cmd));
|
||||
vid_mode = VID_MODE_TEXT;
|
||||
} break;
|
||||
case VID_CMD_SET_PIXEL_MODE: {
|
||||
video_command_t cmd = {
|
||||
.cmd = VIDEO_COMMAND_MODE_SET,
|
||||
.data[0] = VIDEO_MODE_PIXEL,
|
||||
};
|
||||
core1_to_core0_write_blocking(*((uint64_t*)&cmd));
|
||||
vid_mode = VID_MODE_PIXEL;
|
||||
} break;
|
||||
case VID_CMD_CLR: {
|
||||
if(vid_mode == VIDEO_MODE_TEXT){
|
||||
video_command_t cmd = {
|
||||
.cmd = VIDEO_CLR_TEXT
|
||||
};
|
||||
core1_to_core0_write_blocking(*((uint64_t*)&cmd));
|
||||
}else if(vid_mode == VIDEO_MODE_PIXEL){
|
||||
video_command_t cmd = {
|
||||
.cmd = VIDEO_CLR_PIXEL
|
||||
};
|
||||
core1_to_core0_write_blocking(*((uint64_t*)&cmd));
|
||||
}
|
||||
} break;
|
||||
case VID_CMD_TEXT_POS_X: {
|
||||
vid_mode = VID_MODE_TEXT_POS_X;
|
||||
} break;
|
||||
case VID_CMD_PIXEL_POS_X: {
|
||||
vid_mode = VID_MODE_PIXEL_POS_X_1;
|
||||
} break;
|
||||
case VID_CMD_TEXT_POS_Y: {
|
||||
vid_mode = VID_MODE_TEXT_POS_Y;
|
||||
} break;
|
||||
case VID_CMD_PIXEL_POS_Y: {
|
||||
vid_mode = VID_MODE_PIXEL_POS_Y_1;
|
||||
} break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
if((regs.flags & 0x02) == 0x02){
|
||||
// Write to vid_dat happened
|
||||
switch(vid_mode){
|
||||
case VID_MODE_TEXT_POS_X:
|
||||
cursor_x = (regs.vid_data<CHARS_PER_LINE) ? regs.vid_data : cursor_x;
|
||||
break;
|
||||
case VID_MODE_TEXT_POS_Y:
|
||||
cursor_y = (regs.vid_data<CHAR_LINES) ? regs.vid_data : cursor_y;
|
||||
break;
|
||||
|
||||
if (time_reached(next_report)) {
|
||||
char message[48];
|
||||
snprintf(message, sizeof(message), "Z80 writes: %llu\r\n", (unsigned long long)write_count);
|
||||
core1_puts(message);
|
||||
snprintf(message, sizeof(message), "Last address: %02x\r\n", last_addr);
|
||||
core1_puts(message);
|
||||
snprintf(message, sizeof(message), "Last data: %02x\r\n", last_data);
|
||||
core1_puts(message);
|
||||
next_report = make_timeout_time_ms(100);
|
||||
case VID_MODE_PIXEL_POS_X_1:
|
||||
pixel_x = pixel_x&0xff00 | regs.vid_data;
|
||||
vid_mode = VID_MODE_PIXEL_POS_X_2;
|
||||
break;
|
||||
case VID_MODE_PIXEL_POS_X_2:
|
||||
pixel_x = pixel_x&0x00ff | regs.vid_data<<8;
|
||||
vid_mode = VID_MODE_PIXEL_POS_X_1;
|
||||
pixel_x = (pixel_x<VIDEO_WIDTH) ? pixel_x : 0;
|
||||
break;
|
||||
case VID_MODE_PIXEL_POS_Y_1:
|
||||
pixel_y = pixel_y&0xff00 | regs.vid_data;
|
||||
vid_mode = VID_MODE_PIXEL_POS_Y_2;
|
||||
break;
|
||||
case VID_MODE_PIXEL_POS_Y_2:
|
||||
pixel_y = pixel_y&0x00ff | regs.vid_data<<8;
|
||||
vid_mode = VID_MODE_PIXEL_POS_Y_1;
|
||||
pixel_y = (pixel_y<VIDEO_HEIGHT) ? pixel_y : 0;
|
||||
break;
|
||||
|
||||
case VID_MODE_PIXEL:{
|
||||
video_command_t cmd = {
|
||||
.cmd = VIDEO_WRITE_PIXEL,
|
||||
.data[0] = regs.vid_data,
|
||||
.data[1] = pixel_x,
|
||||
.data[2] = pixel_y,
|
||||
};
|
||||
pixel_x = (pixel_x<VIDEO_WIDTH-8) ? pixel_x+8 : pixel_x;
|
||||
core1_to_core0_write_blocking(*((uint64_t*)&cmd));
|
||||
} break;
|
||||
|
||||
case VID_MODE_TEXT:
|
||||
default:
|
||||
core1_putc(regs.vid_data);
|
||||
}
|
||||
}
|
||||
|
||||
tight_loop_contents();
|
||||
|
||||
28
core1.h
28
core1.h
@@ -1,3 +1,31 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include "pico/stdlib.h"
|
||||
|
||||
enum VIDEO_COMMAND{
|
||||
VIDEO_COMMAND_MODE_SET, // data[0] VIDEO_MODE
|
||||
VIDEO_WRITE_TEXT, // write data[0] to (data[1],data[2])
|
||||
VIDEO_WRITE_PIXEL, // write 8 pixels (low byte of data[0]) to (data[1],daa[2])
|
||||
VIDEO_SCROLL_TEXT, // Sroll text up
|
||||
VIDEO_CLR_TEXT, // Clear screen
|
||||
VIDEO_CLR_PIXEL, // Clear screen
|
||||
};
|
||||
|
||||
enum VIDEO_MODE{
|
||||
VIDEO_MODE_TEXT, // Text mode
|
||||
VIDEO_MODE_PIXEL, // Pixel mode
|
||||
};
|
||||
|
||||
typedef struct __packed{
|
||||
uint16_t cmd;
|
||||
uint16_t data[3];
|
||||
} video_command_t; // fits inside uint64_t
|
||||
|
||||
typedef struct __packed{
|
||||
uint8_t vid_cmd;
|
||||
uint8_t vid_data;
|
||||
uint8_t unused[2];
|
||||
uint32_t flags;
|
||||
} registers_t; // fits inside uint64_t
|
||||
|
||||
void core1_entry();
|
||||
37
core_comm.c
37
core_comm.c
@@ -3,18 +3,43 @@
|
||||
#include "pico/multicore.h"
|
||||
#include "pico/platform.h"
|
||||
|
||||
bool core1_to_core0_try_write(uint32_t data) {
|
||||
bool core1_to_core0_try_write(uint64_t data) {
|
||||
hard_assert(get_core_num() == 1);
|
||||
return multicore_fifo_push_timeout_us(data, 0);
|
||||
if (!multicore_fifo_push_timeout_us((uint32_t)data, 0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Once the low word is committed, complete the pair to preserve framing.
|
||||
multicore_fifo_push_blocking((uint32_t)(data >> 32));
|
||||
return true;
|
||||
}
|
||||
|
||||
void core1_to_core0_write_blocking(uint32_t data) {
|
||||
void core1_to_core0_write_blocking(uint64_t data) {
|
||||
hard_assert(get_core_num() == 1);
|
||||
multicore_fifo_push_blocking(data);
|
||||
multicore_fifo_push_blocking((uint32_t)data);
|
||||
multicore_fifo_push_blocking((uint32_t)(data >> 32));
|
||||
}
|
||||
|
||||
bool core0_try_read_from_core1(uint32_t *data) {
|
||||
bool core0_try_read_from_core1(uint64_t *data) {
|
||||
static bool have_low_word;
|
||||
static uint32_t low_word;
|
||||
|
||||
hard_assert(get_core_num() == 0);
|
||||
hard_assert(data != NULL);
|
||||
return multicore_fifo_pop_timeout_us(0, data);
|
||||
|
||||
if (!have_low_word) {
|
||||
if (!multicore_fifo_pop_timeout_us(0, &low_word)) {
|
||||
return false;
|
||||
}
|
||||
have_low_word = true;
|
||||
}
|
||||
|
||||
uint32_t high_word;
|
||||
if (!multicore_fifo_pop_timeout_us(0, &high_word)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*data = (uint64_t)low_word | ((uint64_t)high_word << 32);
|
||||
have_low_word = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -3,15 +3,16 @@
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// The RP2350 multicore FIFO holds eight 32-bit messages in each direction.
|
||||
// The RP2350 multicore FIFO is physically 32 bits wide. Each logical 64-bit
|
||||
// message is transferred low word first, followed by its high word.
|
||||
// These functions intentionally enforce the sending/receiving core roles.
|
||||
|
||||
// Try to send one word from core 1. Returns false instead of waiting when the
|
||||
// FIFO is full.
|
||||
bool core1_to_core0_try_write(uint32_t data);
|
||||
bool core1_to_core0_try_write(uint64_t data);
|
||||
|
||||
// Send one word from core 1, waiting until core 0 has made FIFO space.
|
||||
void core1_to_core0_write_blocking(uint32_t data);
|
||||
void core1_to_core0_write_blocking(uint64_t data);
|
||||
|
||||
// Try to read one word on core 0. Returns false when no message is available.
|
||||
bool core0_try_read_from_core1(uint32_t *data);
|
||||
bool core0_try_read_from_core1(uint64_t *data);
|
||||
|
||||
130
main.c
130
main.c
@@ -15,87 +15,49 @@
|
||||
// --------------------------------
|
||||
|
||||
static video_framebuffer_t fb0, fb1;
|
||||
static video_framebuffer_t pixel_mode_buffer;
|
||||
static text_mode_buffer_t text_mode_buffer;
|
||||
|
||||
static volatile bool framebuffer_switched = true;
|
||||
static bool text_mode = true;
|
||||
static bool text_mode_dirty = true;
|
||||
static uint console_row = 0;
|
||||
static uint console_col = 0;
|
||||
|
||||
void framebuffer_switched_cb(){
|
||||
framebuffer_switched = true;
|
||||
}
|
||||
|
||||
static void text_mode_clear_buffer(void) {
|
||||
static void text_mode_clear_buffer() {
|
||||
memset(text_mode_buffer, 0, sizeof(text_mode_buffer));
|
||||
console_row = 0;
|
||||
console_col = 0;
|
||||
}
|
||||
|
||||
static void text_mode_scroll_up(void) {
|
||||
static void text_mode_scroll_up() {
|
||||
memmove(&text_mode_buffer[0][0],
|
||||
&text_mode_buffer[1][0],
|
||||
(CHAR_LINES - 1) * CHARS_PER_LINE * sizeof(text_mode_buffer[0][0]));
|
||||
memset(text_mode_buffer[CHAR_LINES - 1], 0, CHARS_PER_LINE * sizeof(text_mode_buffer[0][0]));
|
||||
}
|
||||
|
||||
static void text_mode_newline(void) {
|
||||
console_col = 0;
|
||||
if (console_row + 1 >= CHAR_LINES) {
|
||||
text_mode_scroll_up();
|
||||
static void pixel_mode_write_byte(uint16_t x, uint16_t y, uint8_t pixels) {
|
||||
if (x >= VIDEO_WIDTH || y >= VIDEO_HEIGHT) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint bit_index = 0; bit_index < 8 && x + bit_index < VIDEO_WIDTH;
|
||||
bit_index++) {
|
||||
uint pixel_x = x + bit_index;
|
||||
uint word = pixel_x >> 5;
|
||||
uint bit = 31u - (pixel_x & 31u);
|
||||
uint32_t mask = 1u << bit;
|
||||
|
||||
if ((pixels & (1u << (7u - bit_index))) != 0) {
|
||||
pixel_mode_buffer[y][word] |= mask;
|
||||
} else {
|
||||
console_row++;
|
||||
pixel_mode_buffer[y][word] &= ~mask;
|
||||
}
|
||||
}
|
||||
|
||||
static void text_mode_put_char(char c) {
|
||||
if (c == '\r') {
|
||||
console_col = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (c == '\n') {
|
||||
text_mode_newline();
|
||||
return;
|
||||
}
|
||||
|
||||
if (c == '\b' || c == 0x7f) {
|
||||
if (console_col > 0) {
|
||||
console_col--;
|
||||
text_mode_buffer[console_row][console_col] = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if ((unsigned char)c < 32 || (unsigned char)c > 126) {
|
||||
return;
|
||||
}
|
||||
|
||||
text_mode_buffer[console_row][console_col] = c;
|
||||
|
||||
console_col++;
|
||||
if (console_col >= CHARS_PER_LINE) {
|
||||
text_mode_newline();
|
||||
}
|
||||
}
|
||||
|
||||
static void text_mode_puts(const char *s) {
|
||||
while (*s) {
|
||||
text_mode_put_char(*s++);
|
||||
}
|
||||
}
|
||||
|
||||
static void poll_usb_console(void) {
|
||||
int ch;
|
||||
while ((ch = getchar_timeout_us(0)) != PICO_ERROR_TIMEOUT) {
|
||||
text_mode_put_char((char)ch);
|
||||
putchar(ch);
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
stdio_init_all();
|
||||
multicore_launch_core1(core1_entry);
|
||||
|
||||
init_font_cache();
|
||||
@@ -104,33 +66,57 @@ void main() {
|
||||
video_init(fb0);
|
||||
|
||||
text_mode_clear_buffer();
|
||||
text_mode_puts("+------------------------------+\r\n");
|
||||
text_mode_puts("| Pico-PAL Text Mode |\r\n");
|
||||
text_mode_puts("+------------------------------+\r\n");
|
||||
|
||||
gpio_init(25);
|
||||
gpio_set_dir(25, GPIO_OUT);
|
||||
memset(pixel_mode_buffer, 0, sizeof(pixel_mode_buffer));
|
||||
|
||||
int fbnum = 1;
|
||||
video_framebuffer_ptr_t fbs[] = {fb0, fb1};
|
||||
while (true) {
|
||||
poll_usb_console();
|
||||
|
||||
uint32_t value;
|
||||
while (core0_try_read_from_core1(&value)) {
|
||||
char ch = (char)value;
|
||||
text_mode_put_char((char)ch);
|
||||
putchar(ch);
|
||||
video_command_t cmd;
|
||||
while (core0_try_read_from_core1((uint64_t*)&cmd)) {
|
||||
switch(cmd.cmd){
|
||||
case VIDEO_COMMAND_MODE_SET:{
|
||||
switch(cmd.data[0]){
|
||||
case VIDEO_MODE_TEXT:
|
||||
text_mode = true;
|
||||
break;
|
||||
case VIDEO_MODE_PIXEL:
|
||||
text_mode = false;
|
||||
break;
|
||||
default:
|
||||
}
|
||||
} break;
|
||||
case VIDEO_WRITE_TEXT:{
|
||||
char c = (char)cmd.data[0];
|
||||
int posx = ((int)cmd.data[1] >= CHARS_PER_LINE) ? CHARS_PER_LINE-1 : (int)cmd.data[1];
|
||||
int posy = ((int)cmd.data[2] >= CHAR_LINES) ? CHAR_LINES-1 : (int)cmd.data[2];
|
||||
text_mode_buffer[posy][posx] = c;
|
||||
} break;
|
||||
case VIDEO_WRITE_PIXEL:{
|
||||
pixel_mode_write_byte(cmd.data[1], cmd.data[2],
|
||||
(uint8_t)cmd.data[0]);
|
||||
} break;
|
||||
case VIDEO_SCROLL_TEXT:{
|
||||
text_mode_scroll_up();
|
||||
} break;
|
||||
case VIDEO_CLR_TEXT:{
|
||||
text_mode_clear_buffer();
|
||||
} break;
|
||||
case VIDEO_CLR_PIXEL:{
|
||||
memset(pixel_mode_buffer, 0, sizeof(pixel_mode_buffer));
|
||||
} break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// Check if need to draw new frame
|
||||
if(framebuffer_switched){
|
||||
gpio_put(25, fbnum);
|
||||
|
||||
if(text_mode){
|
||||
draw_text_mode(fbs[fbnum], text_mode_buffer);
|
||||
} else {
|
||||
memcpy(fbs[fbnum], pixel_mode_buffer,
|
||||
sizeof(pixel_mode_buffer));
|
||||
video_set_framebuffer(fbs[fbnum]);
|
||||
}
|
||||
|
||||
fbnum = (fbnum+1)%2;
|
||||
framebuffer_switched = false;
|
||||
}
|
||||
|
||||
@@ -4,17 +4,17 @@
|
||||
|
||||
// Text mode config
|
||||
#define CHARS_PER_LINE 60
|
||||
#define CHAR_LINES 24
|
||||
#define CHAR_LINES 18
|
||||
#define CHAR_SCALE 2
|
||||
|
||||
// Text origin in framebuffer pixels. Set both to 0 for the top-left corner.
|
||||
#define TEXT_MARGIN_LEFT (30)
|
||||
#define TEXT_MARGIN_TOP (42)
|
||||
#define TEXT_MARGIN_TOP (40)
|
||||
|
||||
// Crop glyph pixels before scaling. Padding is split across both sides; for
|
||||
// odd values, the extra pixel is removed from the right or bottom.
|
||||
#define GLYPH_PADDING_X 1
|
||||
#define GLYPH_PADDING_Y 4
|
||||
#define GLYPH_PADDING_Y 0
|
||||
|
||||
// Set to 1 for a white background with black text, or 0 for the default
|
||||
// black background with white text.
|
||||
|
||||
61
z80_bus.pio
61
z80_bus.pio
@@ -1,10 +1,59 @@
|
||||
.program z80_write_monitor
|
||||
; Z80 register bus on PIO1.
|
||||
;
|
||||
; GP0 /OE, GP1 DIR, GP2..9 D0..D7, GP10 /IOREQ, GP11 /RD,
|
||||
; GP12 CS (A7), GP13..14 A0..A1, GP15 CLK, GP16 /WAIT.
|
||||
;
|
||||
; side-set values for GP1:GP0:
|
||||
; 0 = B->A enabled, 1 = B->A disabled
|
||||
; 2 = A->B enabled, 3 = A->B disabled
|
||||
|
||||
.program z80_bus
|
||||
.side_set 2 opt
|
||||
|
||||
.wrap_target
|
||||
wait 0 gpio 10 ; /IOREQ asserted
|
||||
wait 0 gpio 10
|
||||
nop [1] ; allow synchronized bus signals to settle
|
||||
mov isr, null
|
||||
; IN base is GP0. Capture /OE, DIR, D0-D7, /IOREQ, /RD, CS,
|
||||
; A0 and A1 in their corresponding GPIO bit positions.
|
||||
in pins, 15
|
||||
in pins, 15 ; snapshot GP0..GP14
|
||||
|
||||
; Decode /RD (bit 11) and CS (bit 12), preserving ISR for core 1.
|
||||
mov osr, isr
|
||||
out null, 11
|
||||
out x, 1 ; X = /RD
|
||||
out y, 1 ; Y = CS
|
||||
jmp !y cycle_done
|
||||
jmp !x read_cycle
|
||||
|
||||
write_cycle:
|
||||
push block
|
||||
wait 1 gpio 10 ; do not count this bus cycle again
|
||||
jmp cycle_done
|
||||
|
||||
read_cycle:
|
||||
set pins, 0 side 1 ; assert /WAIT, disable transceiver
|
||||
push block ; send address request to core 1
|
||||
pull block side 1 ; wait for response byte
|
||||
|
||||
; Prepare data and direction while the transceiver remains disabled.
|
||||
mov x, osr side 1
|
||||
mov osr, ~null side 3 ; select A->B
|
||||
out pindirs, 8 side 3 ; GP2..GP9 outputs
|
||||
mov osr, x side 3
|
||||
out pins, 8 side 3 ; put response on A-side pins
|
||||
|
||||
; Keep /WAIT asserted through a real falling Z80 clock edge. PIO observes
|
||||
; the edge through its synchronizer and releases /WAIT just afterwards,
|
||||
; guaranteeing that the Z80 sampled it low and inserted a wait state.
|
||||
wait 1 gpio 15 side 3
|
||||
wait 0 gpio 15 side 3
|
||||
set pins, 1 side 2 ; enable A->B and release /WAIT
|
||||
|
||||
wait 1 gpio 11 side 2 ; wait for /RD to deassert
|
||||
nop side 3 ; disable before changing direction
|
||||
mov osr, null side 3
|
||||
out pindirs, 8 side 3 ; GP2..GP9 inputs
|
||||
nop side 1 ; select B->A while disabled
|
||||
nop side 0 ; enable B->A for write reception
|
||||
|
||||
cycle_done:
|
||||
wait 1 gpio 10
|
||||
.wrap
|
||||
|
||||
Reference in New Issue
Block a user