/**
* @file mega6502.c
* @brief Implementação corrigida de plataforma 8-bit usando Arduino Mega e 6502
*
* Interface entre Arduino Mega 2560 e Custom Chip 6502 do Wokwi
* - Arduino fornece RAM, ROM e interface I/O
* - 6502 Custom Chip executa instruções
*
* Mapeamento de Memória:
* 0x0000-0x0FFF: RAM (4KB)
* 0xD000-0xD013: PIA (6821 simplificado - UART)
* 0xE000-0xEFFF: ROM Externa (EROM.h)
* 0xF000-0xFFFF: ROM Interna (FROM.h)
*
* Created: 05/11/2025
* Copyright (c) 2025 Anderson Costa
*/
// Define F_CPU ANTES de qualquer include que use delays ou setbaud
#ifndef F_CPU
#define F_CPU 16000000UL // 16 MHz - frequência do Arduino Mega
#endif
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <util/delay.h>
// Inclui settings.h para definir BAUD ANTES de setbaud.h
#include "settings.h"
// Agora pode incluir setbaud.h com BAUD e F_CPU definidos
#include <util/setbaud.h>
#include <stdio.h>
#include "EROM.h"
#include "FROM.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)
// Memória
static uint8_t ram[0x1000]; // 4KB de RAM
/**
* Lê byte da memória baseado no endereço fornecido
*/
static uint8_t mem_read(void) {
const uint8_t
addrh = PINC, // Byte alto do endereço (A8-A15)
addrl = PINA; // Byte baixo do endereço (A0-A7)
const uint16_t addr = ADDR;
uint8_t val = 0x00;
switch (addr >> 12) {
default:
// Espaço não mapeado - retorna 0x00
return 0x00;
case 0x0:
// RAM: 0x0000-0x0FFF
val = ram[addr];
return val;
case 0xd:
// PIA (6821 simplificado): 0xD000-0xD013
switch (addrl) {
case 0x10:
// Lê caractere recebido da serial
val = 0x80 | UDR0;
break;
case 0x11:
// Status de recepção (RXC0)
val = (UCSR0A & _BV(RXC0)) ? 0xFF : 0x00;
break;
case 0x12:
// Status de transmissão (UDRE0)
val = (UCSR0A & _BV(UDRE0)) ? 0x00 : 0xFF;
break;
default:
val = 0x00;
break;
}
return val;
case 0xe:
// ROM Externa: 0xE000-0xEFFF
val = pgm_read_byte(erom + (addr & 0x0fff));
return val;
case 0xf:
// ROM Interna: 0xF000-0xFFFF
val = pgm_read_byte(from + (addr & 0x0fff));
return val;
}
}
/**
* Escreve byte na memória baseado no endereço fornecido
*/
static void mem_write(void) {
const uint8_t
addrh = PINC, // Byte alto do endereço
addrl = PINA, // Byte baixo do endereço
val = PINL; // Dados a serem escritos
const uint16_t addr = ADDR;
switch (addr >> 12) {
default:
// Espaço não mapeado - ignora escrita
break;
case 0x0:
// RAM: 0x0000-0x0FFF
ram[addr] = val;
break;
case 0xd:
// PIA (6821 simplificado)
switch (addrl) {
case 0x12:
// Envia caractere pela serial
loop_until_bit_is_set(UCSR0A, UDRE0);
UDR0 = val & 0x7f;
break;
case 0x11:
case 0x13:
// Ignora configurações do PIA
break;
default:
break;
}
break;
}
}
/**
* Envia caractere pela UART
*/
static int uart_putchar(char c, FILE *stream) {
if (c == '\r')
uart_putchar('\n', stream);
loop_until_bit_is_set(UCSR0A, UDRE0);
UDR0 = c;
return 1;
}
static FILE uartout = {0};
/**
* Inicializa UART para comunicação serial
*/
void uart_init(void) {
// Configura baud rate
UBRR0H = UBRRH_VALUE;
UBRR0L = UBRRL_VALUE;
#if USE_2X
UCSR0A |= _BV(U2X0);
#else
UCSR0A &= ~(_BV(U2X0));
#endif
// 8 bits de dados, sem paridade, 1 stop bit
UCSR0C = _BV(UCSZ01) | _BV(UCSZ00);
// Habilita RX e TX
UCSR0B = _BV(RXEN0) | _BV(TXEN0);
// Configura stdout para UART
fdev_setup_stream(&uartout, uart_putchar, NULL, _FDEV_SETUP_WRITE);
stdout = &uartout;
}
/**
* Inicializa portas de I/O para comunicação com 6502
*/
void port_init(void) {
// Configura pinos de controle
DDRB |= (1 << PB0); // CLOCK_OUT (pino 53) como saída
DDRB |= (1 << PB1); // RESET_PIN (pino 52) como saída
DDRB &= ~(1 << PB2); // RW_PIN (pino 51) como entrada
// Inicia com RESET alto
PORTB |= (1 << PB1); // RESET alto
PORTB &= ~(1 << PB0); // CLOCK baixo
// PORTA = A0-A7 (entrada)
DDRA = 0x00;
PORTA = 0x00;
// PORTC = A8-A15 (entrada)
DDRC = 0x00;
PORTC = 0x00;
// PORTL = D0-D7 (será configurado dinamicamente)
DDRL = 0x00;
PORTL = 0x00;
// Gera pulso de RESET (mínimo 2 ciclos de clock)
_delay_ms(10);
PORTB &= ~(1 << PB1); // RESET baixo
// Gera 7 ciclos de clock durante reset
for (int i = 0; i < 7; i++) {
PORTB |= (1 << PB0); // CLOCK alto
_delay_us(1);
PORTB &= ~(1 << PB0); // CLOCK baixo
_delay_us(1);
}
_delay_ms(10);
PORTB |= (1 << PB1); // RESET alto
_delay_ms(10);
}
int main(void) {
// Inicialização
uart_init();
printf("6502 System Reset\n");
printf("RAM: 4KB @ 0x0000-0x0FFF\n");
printf("ROM: 8KB @ 0xE000-0xFFFF\n\n");
port_init();
// Loop principal - processa ciclos de bus do 6502
while (1) {
// Fase 1: Clock LOW - 6502 coloca endereço e R/W no barramento
PORTB &= ~(1 << PB0); // CLOCK baixo
// Aguarda estabilização do barramento (mínimo 4 NOPs @ 16MHz)
__asm__ volatile(
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
);
// Lê sinal R/W do 6502
uint8_t rw = (PINB & (1 << PB2)) ? 1 : 0;
if (rw) {
// ========== CICLO DE LEITURA ==========
// 6502 quer ler da memória
// Configura barramento de dados como saída
DDRL = 0xFF;
// Lê memória e coloca no barramento
uint8_t data = mem_read();
PORTL = data;
} else {
// ========== CICLO DE ESCRITA ==========
// 6502 quer escrever na memória
// Configura barramento de dados como entrada
DDRL = 0x00;
PORTL = 0x00;
}
// Fase 2: Clock HIGH - 6502 lê/escreve dados
PORTB |= (1 << PB0); // CLOCK alto
// Aguarda 6502 processar
__asm__ volatile(
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
);
// Se foi ciclo de escrita, processa agora
if (!rw) {
mem_write();
}
}
return 0;
}