/**
* @file mega6502.c
* @brief An experimental implementation of a minimal 8-bit platform using an
* Arduino Mega and 6502 chip.
*
* This project is an attempt to build a minimal 8-bit platform using a
* combination of an Arduino microcontroller and a 6502 microprocessor. The
* platform provides an environment for experimenting with low-level programming,
* embedded systems, and retro computing. The system includes a simple input/output
* interface, a small amount of RAM and ROM storage, and a basic operating system
* for managing memory and I/O. The project is intended for educational and
* experimental purposes, and provides a simple starting point for building and
* exploring custom embedded systems and devices.
*
* Created on 19/02/2023
* Copyright (c) 2023 Anderson Costa
*/
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <util/delay.h>
#include <stdio.h>
#include "settings.h"
#include "EROM.h"
#include "FROM.h"
#include "ROM.h"
#include <util/setbaud.h>
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif
#define ADDR ((addrh << 8) | addrl)
static uint8_t ram[0x1000]; // 4KB of RAM memory
static uint8_t pia[0x14]; // 20 bytes of memory for the PIA chip
static uint8_t mem_read()
{
const uint8_t
addrh = PINC, // Read high address byte from port C
addrl = PINA; // Read low address byte from port A
const uint16_t addr = ADDR; // Get the complete 16-bit address
uint8_t val;
switch (addr >> 12) { // Check the upper 4 bits of the address
default:
// Nothing in the address space, just return zero
return 0x00;
case 0x0:
val = ram[addr]; // Read from RAM
return val;
case 0xd:
// Fake 6821
switch (addrl) { // Check the low byte of the address
case 0x10:
val = 0x80 | UDR0; // Read the received character from the serial port
break;
case 0x11:
val = UCSR0A & _BV(RXC0); // Check if there is a character waiting to be read from the serial port
break;
case 0x12:
val = UCSR0A & _BV(UDRE0) ? 0x00 : 0xFF; // Check if the serial port is ready to send a character
break;
default:
printf("pia: invalid read at %04x\n", addr); // Report an error for reads to undefined areas
val = 0;
break;
}
return val;
case 0xe:
val = pgm_read_byte(erom + (addr & 0x0fff)); // Read from external ROM
return val;
case 0xf:
val = pgm_read_byte(from + (addr & 0x0fff)); // Read from internal ROM
return val;
}
}
static void mem_write()
{
const uint8_t
addrh = PINC, // Read high address byte from port C
addrl = PINA, // Read low address byte from port A
val = PINL; // Read data byte from port L
const uint16_t addr = ADDR; // Get the complete 16-bit address
switch (addr >> 12) { // Check the upper 4 bits of the address
default:
// Nothing in the address space ignore the write
break;
case 0x0:
ram[addr] = val; // Write to RAM
break;
case 0xd:
switch (addrl) {
default:
printf("PIA: invalid write at %04x: %02x\n", addr, val); // Report an error for writes to undefined areas
break;
case 0x12:
putchar(val & 0x7f); // Send the character to the serial port
break;
case 0x11:
// Ignore write to $D011, PIA is not configurable
break;
case 0x13:
// Ignore write to $D013, PIA is not configurable
break;
}
break;
}
}
static int uart_getchar(FILE *stream)
{
// Wait for incoming data on the serial port
loop_until_bit_is_set(UCSR0A, RXC0);
// Read the incoming byte
return UDR0;
}
static int uart_putchar(char c, FILE *stream) {
// Convert CR to CRLF
if (c == '\r')
uart_putchar('\n', stream);
// Wait for the UART Data Register to be empty
loop_until_bit_is_set(UCSR0A, UDRE0);
// Send the byte to the serial port
UDR0 = c;
// Return the number of bytes transmitted
return 1;
}
static FILE uartout = {0};
void uart_init()
{
// Set the baud rate for the serial port
UBRR0H = UBRRH_VALUE;
UBRR0L = UBRRL_VALUE;
// Enable/disable double speed operation
#if USE_2X
UCSR0A |= _BV(U2X0); // Enable
#else
UCSR0A &= ~(_BV(U2X0));
#endif
// 8-bit data
UCSR0C = _BV(UCSZ01) | _BV(UCSZ00);
// Enable RX and TX
UCSR0B = _BV(RXEN0) | _BV(TXEN0);
fdev_setup_stream(&uartout, uart_putchar, NULL, _FDEV_SETUP_WRITE);
stdout = &uartout;
}
void port_init()
{
// Set CLOCK and RESET pins as outputs
DDRB |= (1 << CLOCK) | (1 << RESET);
// Set RW pin as an input
DDRB &= ~(1 << RW);
// Set RESET pin high
PORTB |= (1 << RESET);
// Setup PORTC to read the low byte of the address
DDRC = 0x00;
PORTC = 0x00;
// Setup PORTA to do the same for the high byte
DDRA = 0x00;
PORTA = 0x00;
// Send reset pulse
PORTB &= ~(1 << RESET); // RESET low
PORTB |= (1 << CLOCK); // CLOCK high
PORTB &= ~(1 << CLOCK); // CLOCK low
PORTB |= (1 << CLOCK); // CLOCK high
PORTB &= ~(1 << CLOCK); // CLOCK low
PORTB |= (1 << CLOCK); // CLOCK high
PORTB &= ~(1 << CLOCK); // CLOCK low
PORTB |= (1 << CLOCK); // CLOCK high
PORTB &= ~(1 << CLOCK); // CLOCK low
PORTB |= (1 << CLOCK); // CLOCK high
PORTB &= ~(1 << CLOCK); // CLOCK low
PORTB |= (1 << CLOCK); // CLOCK high
PORTB |= (1 << RESET); // RESET high
}
int main()
{
uart_init(); // Initialize the USART interface for serial communication
printf("RESET\n"); // Send the "RESET" message to the serial port
port_init(); // Initialize the I/O port pins for communication with the external memory
while (1) {
cli();
DDRB |= (1 << RW); // Configure RW pin as an output
PORTB &= ~(1 << RW); // Set RW pin low to indicate a write operation
// _delay_us(1); // Wait for the signals to stabilize
__asm__("nop\n\t""nop\n\t""nop\n\t""nop\n\t"); // 4 * 62.5ns delay @ 16mhz
PORTB &= ~(1 << CLOCK); // Set CLOCK pin low to signal the start of a read or write operation
sei(); // Enable interrupts
if (PINB & (1 << RESET)) {
// Read cycle
DDRL = 0xff; // Configure PORTL pins as outputs to read data from external memory
PORTL = mem_read(); // Read the data from the external memory and send it over the USART
} else {
// Write cycle
DDRL = 0x00; // Configure PORTL pins as inputs to write data to external memory
PORTL = 0; // Clear PORTL to prepare for data to be written
mem_write(); // Receive the data over the USART and write it to the external memory
}
}
return 0;
}