Simple monitor program
This commit is contained in:
@@ -6,8 +6,6 @@ METAL_ASM_SRCFILES := $(wildcard *.asm)
|
||||
METAL_RELFILES := $(METAL_ASM_SRCFILES:%.asm=$(BUILDDIR)/metal/%.rel) \
|
||||
$(METAL_C_SRCFILES:%.c=$(BUILDDIR)/metal/%.rel)
|
||||
|
||||
MUSIC_FILE := updraft_flight
|
||||
|
||||
METAL_CC := sdcc
|
||||
METAL_AS := sdasz80
|
||||
METAL_MAKEBIN := makebin
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
# Z80 UART monitor
|
||||
|
||||
This ROM provides a small interactive monitor on the UART/CTC card at I/O base
|
||||
`20`. Connect a terminal at **2400 baud, 8 data bits, no parity, 1 stop bit**.
|
||||
Commands are case-insensitive and all numeric arguments are hexadecimal; an
|
||||
optional `0x` prefix is accepted.
|
||||
|
||||
| Command | Action |
|
||||
| --- | --- |
|
||||
| `D 8000` | Dump 40 bytes starting at address 8000 |
|
||||
| `D 8000 20` | Dump 20 bytes starting at address 8000 |
|
||||
| `W 8000 3E 41 D3 25 C9` | Write bytes starting at address 8000 |
|
||||
| `I 20` | Read I/O port 20 |
|
||||
| `O 20 57` | Write 57 to I/O port 20 |
|
||||
| `G 8000` | Execute code at address 8000 |
|
||||
| `HELP` | Show the command summary |
|
||||
|
||||
`G` transfers control with a return address on the stack, so code ending in
|
||||
`RET` returns to the monitor. Code that changes the stack pointer, interrupt
|
||||
mode, or UART/CTC setup must restore it before returning.
|
||||
|
||||
Build the ROM and programmer-format images with:
|
||||
|
||||
```sh
|
||||
make
|
||||
```
|
||||
|
||||
The outputs are `build/metal/monitor.bin` (32 KiB ROM image) and
|
||||
`build/metal/monitor.prog.bin` (compact programmer image).
|
||||
@@ -7,6 +7,7 @@ DELAY_LOOPS_PER_MS = 40
|
||||
.globl _io_in
|
||||
.globl _enable_interrupts
|
||||
.globl _disable_interrupts
|
||||
.globl _jump_to
|
||||
.globl s__DATA
|
||||
.globl l__DATA
|
||||
.globl s__INITIALIZER
|
||||
@@ -153,6 +154,11 @@ _io_in::
|
||||
in a, (c)
|
||||
ret
|
||||
|
||||
; Execute code at HL. Because this routine is entered with CALL, a RET in the
|
||||
; target returns to the monitor immediately after jump_to().
|
||||
_jump_to::
|
||||
jp (hl)
|
||||
|
||||
; Approximate millisecond delay.
|
||||
; Input: HL = delay in milliseconds.
|
||||
_delay_ms::
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
#ifndef __H_BOOT
|
||||
#define __H_BOOT
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
void io_out(uint8_t a, uint8_t d);
|
||||
uint8_t io_in(uint8_t d);
|
||||
void delay_ms(uint16_t t);
|
||||
void enable_interrupts();
|
||||
void disable_interrupts();
|
||||
void enable_interrupts(void);
|
||||
void disable_interrupts(void);
|
||||
void jump_to(uint16_t address);
|
||||
|
||||
#endif
|
||||
+3
-1
@@ -1,7 +1,9 @@
|
||||
#ifndef __H_CIRC_BUF
|
||||
#define __H_CIRC_BUF
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define BUFFER_SIZE 5
|
||||
#define BUFFER_SIZE 64
|
||||
|
||||
typedef struct {
|
||||
char data[BUFFER_SIZE];
|
||||
|
||||
@@ -2,108 +2,362 @@
|
||||
#include "boot.h"
|
||||
#include "circ_buf.h"
|
||||
|
||||
#define CPUREG 0x00
|
||||
#define CPUREG_ROM_DISABLE 0x01
|
||||
#define CPUREG_BANK_SELECT 0x02
|
||||
#define CPUREG_BANK_ENABLE 0x04
|
||||
#define CPUREG_GPO_0 0x08
|
||||
#define CPUREG_GPO_1 0x10
|
||||
#define CPUREG_GPO_2 0x20
|
||||
#define CPUREG_GPO_3 0x40
|
||||
#define CPUREG_GPO_4 0x80
|
||||
#define CPUREG_GPO_MASK 0xf8
|
||||
|
||||
#define UART_CTC_CARD_BASE 0x20
|
||||
#define CTC_BASE (UART_CTC_CARD_BASE + 0x00)
|
||||
#define CTC_CHANNEL_0 (CTC_BASE + 0)
|
||||
#define CTC_CHANNEL_3 (CTC_BASE + 3)
|
||||
#define UART_BASE (UART_CTC_CARD_BASE + 0x04)
|
||||
#define UART_CONTROL (UART_BASE + 0)
|
||||
#define UART_DATA (UART_BASE + 1)
|
||||
|
||||
#define CTC_BASE (UART_CTC_CARD_BASE+0x00)
|
||||
#define CTC_CHANNEL_0 (CTC_BASE+0)
|
||||
#define CTC_CHANNEL_1 (CTC_BASE+1)
|
||||
#define CTC_CHANNEL_2 (CTC_BASE+2)
|
||||
#define CTC_CHANNEL_3 (CTC_BASE+3)
|
||||
#define LINE_SIZE 80
|
||||
#define MAX_ARGS 40
|
||||
#define DEFAULT_DUMP_LENGTH 0x40
|
||||
#define MAX_DUMP_LENGTH 0x100
|
||||
|
||||
#define UART_BASE (UART_CTC_CARD_BASE+0x04)
|
||||
#define UART_CONTROL (UART_BASE+0)
|
||||
#define UART_DATA (UART_BASE+1)
|
||||
|
||||
volatile uint8_t v;
|
||||
volatile uint8_t uart_tx_busy;
|
||||
volatile circular_buffer_t rx_buf;
|
||||
volatile circular_buffer_t tx_buf;
|
||||
|
||||
void uart_puts(char * c){
|
||||
while(*c){
|
||||
// fill tx buffer till it is full
|
||||
while(*c && buffer_enqueue(&tx_buf, *c)){
|
||||
c++;
|
||||
}
|
||||
// if uart not busy kick off
|
||||
if(!uart_tx_busy){
|
||||
static void uart_kick(void)
|
||||
{
|
||||
char c;
|
||||
|
||||
disable_interrupts();
|
||||
char cc;
|
||||
if(buffer_dequeue(&tx_buf, &cc)){
|
||||
if (!uart_tx_busy && buffer_dequeue(&tx_buf, &c)) {
|
||||
uart_tx_busy = 1;
|
||||
io_out(UART_DATA, cc);
|
||||
io_out(UART_DATA, c);
|
||||
io_out(UART_CONTROL, 0b10110101);
|
||||
}
|
||||
enable_interrupts();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void uart_interrupt(){
|
||||
static void uart_putc(char c)
|
||||
{
|
||||
uint8_t queued;
|
||||
|
||||
do {
|
||||
disable_interrupts();
|
||||
queued = buffer_enqueue(&tx_buf, c);
|
||||
enable_interrupts();
|
||||
if (!queued) uart_kick();
|
||||
} while (!queued);
|
||||
uart_kick();
|
||||
}
|
||||
|
||||
static void uart_puts(const char *s)
|
||||
{
|
||||
while (*s) uart_putc(*s++);
|
||||
}
|
||||
|
||||
static char uart_getc(void)
|
||||
{
|
||||
char c;
|
||||
uint8_t received;
|
||||
|
||||
do {
|
||||
disable_interrupts();
|
||||
received = buffer_dequeue(&rx_buf, &c);
|
||||
enable_interrupts();
|
||||
} while (!received);
|
||||
return c;
|
||||
}
|
||||
|
||||
static void uart_flush(void)
|
||||
{
|
||||
while (uart_tx_busy || !buffer_is_empty(&tx_buf)) uart_kick();
|
||||
}
|
||||
|
||||
void uart_interrupt(void)
|
||||
{
|
||||
uint8_t status = io_in(UART_CONTROL);
|
||||
|
||||
if((status&0x21)!=0){
|
||||
// RDRF or OVRN
|
||||
if ((status & 0x21) != 0) {
|
||||
char c = io_in(UART_DATA);
|
||||
if((status&0x01)!=0){
|
||||
// RDRF
|
||||
buffer_enqueue(&rx_buf, c);
|
||||
if ((status & 0x01) != 0) buffer_enqueue(&rx_buf, c);
|
||||
}
|
||||
}
|
||||
|
||||
if((status&0x02)!=0 && uart_tx_busy){
|
||||
//TXRE
|
||||
if ((status & 0x02) != 0 && uart_tx_busy) {
|
||||
char c;
|
||||
if(buffer_dequeue(&tx_buf, &c)){
|
||||
if (buffer_dequeue(&tx_buf, &c)) {
|
||||
io_out(UART_DATA, c);
|
||||
}else{
|
||||
} else {
|
||||
io_out(UART_CONTROL, 0b10010101);
|
||||
uart_tx_busy = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ctc_interrupt(){
|
||||
void ctc_interrupt(void)
|
||||
{
|
||||
}
|
||||
|
||||
void main(){
|
||||
static char hex_digit(uint8_t value)
|
||||
{
|
||||
value &= 0x0f;
|
||||
return value < 10 ? (char)('0' + value) : (char)('A' + value - 10);
|
||||
}
|
||||
|
||||
static void print_hex8(uint8_t value)
|
||||
{
|
||||
uart_putc(hex_digit(value >> 4));
|
||||
uart_putc(hex_digit(value));
|
||||
}
|
||||
|
||||
static void print_hex16(uint16_t value)
|
||||
{
|
||||
print_hex8((uint8_t)(value >> 8));
|
||||
print_hex8((uint8_t)value);
|
||||
}
|
||||
|
||||
static uint8_t hex_value(char c, uint8_t *value)
|
||||
{
|
||||
if (c >= '0' && c <= '9') {
|
||||
*value = (uint8_t)(c - '0');
|
||||
return 1;
|
||||
}
|
||||
if (c >= 'a' && c <= 'f') {
|
||||
*value = (uint8_t)(c - 'a' + 10);
|
||||
return 1;
|
||||
}
|
||||
if (c >= 'A' && c <= 'F') {
|
||||
*value = (uint8_t)(c - 'A' + 10);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint8_t parse_hex(const char *text, uint16_t *result)
|
||||
{
|
||||
uint16_t value = 0;
|
||||
uint8_t digit;
|
||||
uint8_t digits = 0;
|
||||
|
||||
if (text[0] == '0' && (text[1] == 'x' || text[1] == 'X')) text += 2;
|
||||
while (*text) {
|
||||
if (digits == 4 || !hex_value(*text++, &digit)) return 0;
|
||||
value = (uint16_t)((value << 4) | digit);
|
||||
++digits;
|
||||
}
|
||||
if (digits == 0) return 0;
|
||||
*result = value;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static uint8_t strings_equal(const char *a, const char *b)
|
||||
{
|
||||
char ca;
|
||||
char cb;
|
||||
|
||||
do {
|
||||
ca = *a++;
|
||||
cb = *b++;
|
||||
if (ca >= 'A' && ca <= 'Z') ca += 'a' - 'A';
|
||||
if (cb >= 'A' && cb <= 'Z') cb += 'a' - 'A';
|
||||
if (ca != cb) return 0;
|
||||
} while (ca);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static uint8_t split_line(char *line, char **argv)
|
||||
{
|
||||
uint8_t argc = 0;
|
||||
|
||||
while (*line) {
|
||||
while (*line == ' ' || *line == '\t') ++line;
|
||||
if (!*line) break;
|
||||
if (argc == MAX_ARGS) return argc;
|
||||
argv[argc++] = line;
|
||||
while (*line && *line != ' ' && *line != '\t') ++line;
|
||||
if (*line) *line++ = 0;
|
||||
}
|
||||
return argc;
|
||||
}
|
||||
|
||||
static void print_help(void)
|
||||
{
|
||||
uart_puts(
|
||||
"Commands (hexadecimal numbers):\r\n"
|
||||
" D|DUMP address [length] Dump memory (default 0040, max 0100)\r\n"
|
||||
" W|WRITE address byte ... Write one or more memory bytes\r\n"
|
||||
" I|IN port Read an I/O port\r\n"
|
||||
" O|OUT port byte Write an I/O port\r\n"
|
||||
" G|GO address Execute code; RET returns here\r\n"
|
||||
" H|HELP|? Show this help\r\n");
|
||||
}
|
||||
|
||||
static void command_dump(uint8_t argc, char **argv)
|
||||
{
|
||||
uint16_t address;
|
||||
uint16_t length = DEFAULT_DUMP_LENGTH;
|
||||
uint16_t offset = 0;
|
||||
|
||||
if (argc < 2 || argc > 3 || !parse_hex(argv[1], &address) ||
|
||||
(argc == 3 && !parse_hex(argv[2], &length))) {
|
||||
uart_puts("Usage: D address [length]\r\n");
|
||||
return;
|
||||
}
|
||||
if (length > MAX_DUMP_LENGTH) {
|
||||
uart_puts("Length must be 0100 or less\r\n");
|
||||
return;
|
||||
}
|
||||
while (offset < length) {
|
||||
uint8_t column;
|
||||
uint8_t row_length = (uint8_t)((length - offset) > 16 ? 16 : (length - offset));
|
||||
|
||||
print_hex16((uint16_t)(address + offset));
|
||||
uart_puts(": ");
|
||||
for (column = 0; column < row_length; ++column) {
|
||||
print_hex8(*(volatile uint8_t *)(address + offset + column));
|
||||
uart_putc(' ');
|
||||
}
|
||||
uart_puts("\r\n");
|
||||
offset += row_length;
|
||||
}
|
||||
}
|
||||
|
||||
static void command_write(uint8_t argc, char **argv)
|
||||
{
|
||||
uint16_t address;
|
||||
uint16_t value;
|
||||
uint8_t i;
|
||||
|
||||
if (argc < 3 || !parse_hex(argv[1], &address)) {
|
||||
uart_puts("Usage: W address byte [byte ...]\r\n");
|
||||
return;
|
||||
}
|
||||
for (i = 2; i < argc; ++i) {
|
||||
if (!parse_hex(argv[i], &value) || value > 0xff) {
|
||||
uart_puts("Bad byte: ");
|
||||
uart_puts(argv[i]);
|
||||
uart_puts("\r\n");
|
||||
return;
|
||||
}
|
||||
*(volatile uint8_t *)address++ = (uint8_t)value;
|
||||
}
|
||||
uart_puts("OK\r\n");
|
||||
}
|
||||
|
||||
static void command_in(uint8_t argc, char **argv)
|
||||
{
|
||||
uint16_t port;
|
||||
|
||||
if (argc != 2 || !parse_hex(argv[1], &port) || port > 0xff) {
|
||||
uart_puts("Usage: I port\r\n");
|
||||
return;
|
||||
}
|
||||
print_hex8((uint8_t)port);
|
||||
uart_puts(": ");
|
||||
print_hex8(io_in((uint8_t)port));
|
||||
uart_puts("\r\n");
|
||||
}
|
||||
|
||||
static void command_out(uint8_t argc, char **argv)
|
||||
{
|
||||
uint16_t port;
|
||||
uint16_t value;
|
||||
|
||||
if (argc != 3 || !parse_hex(argv[1], &port) || port > 0xff ||
|
||||
!parse_hex(argv[2], &value) || value > 0xff) {
|
||||
uart_puts("Usage: O port byte\r\n");
|
||||
return;
|
||||
}
|
||||
io_out((uint8_t)port, (uint8_t)value);
|
||||
uart_puts("OK\r\n");
|
||||
}
|
||||
|
||||
static void command_go(uint8_t argc, char **argv)
|
||||
{
|
||||
uint16_t address;
|
||||
|
||||
if (argc != 2 || !parse_hex(argv[1], &address)) {
|
||||
uart_puts("Usage: G address\r\n");
|
||||
return;
|
||||
}
|
||||
uart_puts("Executing at ");
|
||||
print_hex16(address);
|
||||
uart_puts("...\r\n");
|
||||
uart_flush();
|
||||
jump_to(address);
|
||||
uart_puts("Returned to monitor\r\n");
|
||||
}
|
||||
|
||||
static void execute_line(char *line)
|
||||
{
|
||||
char *argv[MAX_ARGS];
|
||||
uint8_t argc = split_line(line, argv);
|
||||
|
||||
if (!argc) return;
|
||||
if (strings_equal(argv[0], "h") || strings_equal(argv[0], "help") ||
|
||||
strings_equal(argv[0], "?")) {
|
||||
print_help();
|
||||
} else if (strings_equal(argv[0], "d") || strings_equal(argv[0], "dump")) {
|
||||
command_dump(argc, argv);
|
||||
} else if (strings_equal(argv[0], "w") || strings_equal(argv[0], "write")) {
|
||||
command_write(argc, argv);
|
||||
} else if (strings_equal(argv[0], "i") || strings_equal(argv[0], "in")) {
|
||||
command_in(argc, argv);
|
||||
} else if (strings_equal(argv[0], "o") || strings_equal(argv[0], "out")) {
|
||||
command_out(argc, argv);
|
||||
} else if (strings_equal(argv[0], "g") || strings_equal(argv[0], "go")) {
|
||||
command_go(argc, argv);
|
||||
} else {
|
||||
uart_puts("Unknown command; type HELP\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
void main(void)
|
||||
{
|
||||
char line[LINE_SIZE];
|
||||
uint8_t length = 0;
|
||||
uint8_t ignore_lf = 0;
|
||||
|
||||
buffer_init(&rx_buf);
|
||||
buffer_init(&tx_buf);
|
||||
|
||||
io_out(CTC_CHANNEL_0, 0x57); // no int, counter @ rising, value follows, soft reset, control word
|
||||
io_out(CTC_CHANNEL_0, 13); // 38.462 kHz out;
|
||||
|
||||
io_out(CTC_CHANNEL_3, 0b11001111); // int, counter @ falling, ext trig, value follows, soft reset, control word
|
||||
io_out(CTC_CHANNEL_0, 0x57);
|
||||
io_out(CTC_CHANNEL_0, 13);
|
||||
io_out(CTC_CHANNEL_3, 0b11001111);
|
||||
io_out(CTC_CHANNEL_3, 1);
|
||||
|
||||
io_out(CTC_CHANNEL_0, 0x00); // vector follows
|
||||
io_out(CTC_CHANNEL_0, 0x00);
|
||||
io_out(CTC_CHANNEL_0, 0x40);
|
||||
|
||||
uart_tx_busy = 0;
|
||||
io_out(UART_CONTROL, 0b00000011); // master reset
|
||||
io_out(UART_CONTROL, 0b10010101); // recv int, 8N1, /16 -> 2403.85 baud
|
||||
|
||||
io_out(UART_CONTROL, 0b00000011);
|
||||
io_out(UART_CONTROL, 0b10010101);
|
||||
enable_interrupts();
|
||||
|
||||
uart_puts("Z80 monitor - v1\r\n");
|
||||
uart_puts("----------------\r\n");
|
||||
uart_puts("\r\nZ80 monitor - v1\r\nType HELP for commands.\r\n> ");
|
||||
|
||||
v = 0;
|
||||
for(;;){
|
||||
char c[2] = {0, 0};
|
||||
while(buffer_dequeue(&rx_buf, c)){
|
||||
uart_puts(c);
|
||||
for (;;) {
|
||||
char c = uart_getc();
|
||||
|
||||
if (c == '\n' && ignore_lf) {
|
||||
ignore_lf = 0;
|
||||
continue;
|
||||
}
|
||||
ignore_lf = 0;
|
||||
if (c == '\r' || c == '\n') {
|
||||
ignore_lf = (c == '\r');
|
||||
line[length] = 0;
|
||||
uart_puts("\r\n");
|
||||
execute_line(line);
|
||||
length = 0;
|
||||
uart_puts("> ");
|
||||
} else if (c == '\b' || c == 0x7f) {
|
||||
if (length) {
|
||||
--length;
|
||||
uart_puts("\b \b");
|
||||
}
|
||||
} else if (c == 0x15) {
|
||||
while (length) {
|
||||
--length;
|
||||
uart_puts("\b \b");
|
||||
}
|
||||
} else if (c >= ' ' && c <= '~' && length < LINE_SIZE - 1) {
|
||||
line[length++] = c;
|
||||
uart_putc(c);
|
||||
} else if (c >= ' ' && c <= '~') {
|
||||
uart_putc('\a');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user