summary

This guide explains what functions in what files you should change to port uARM to another microcontroller/board. It then has more details after the table.

There reset of the code should *not* be changed, unless you know what you're doing exactly.

The big bad table

FUNCTIONUSENOTES
--- FILE: SD.c ---
void sdClockSpeed(Bool fast)sets SPI speed to fast or slowIf you use a hardware spi controller, use this opportunity to reconfigure it for the given speed (slow should be 100-400KHz fast can be anything under 20MHz). For bit-banged versions just save the variable somewhere so that your bit-banging code can use it to decide to be slow or very slow :-)
u8 sdSpiByte(u8 byte)spi bytesend and concurrently receive a byte on the SPI bus
void sdSpiSingleClock()spi clock pulseSince I do not use the CS pin on the SD card (to save a pin), power-up glitching can cause the SD card to think we're in the middle of a byte when the microcontroller thinks we're at a byte boundary. Needless to say this is bad. This function is used by the SD init code to resync with the card. It should move the data line high (very important!) and then pulse the clock line high and back low (min pulse width: 1.25 microseconds)
--- FILE: SoC.c ---
socRun() line "if(!(PIND & 0x10))"This is used to print the current emulator speed. Either change to "if(0)" or change code to read your button state (the if condition should be true if button is low)
--- FILE: main_avr.c ---
int readchar()get a serial port characterReturn a character from the serial port buffer or CHAR_NONE if none is available. This function is called rather often (and regardless of whether the emulated OS asks for it), so you are unlikely to need a buffer.
void writechar(int chr)put a serial port characterWrite a character to your serial port.
void init()Initialize the CPU/boardDo whatever you need in here to initialize your board, pinmux, RTC, clocks, serial port, toaster, etc...
ISR(TIMER3_COMPA_vect)RTC interruptFeel free to remove this, but you do need an RTC. See later functions for details...
u32 rtcCurTime(void)get the RTCReturn the current unix time (or some semblance thereof). How you do this is your own business, just do it.
--- FILE: avr_asm.S (this file can be coded in C as well, if you want) ---
u8 sdSpiByte(u8 byte)see same func in SD.cI wrote it in asm, you don't have to. Details in SD.c's sdSpiByte comment above
void ramRead(u32 addr, u8* buf, u8 sz)Do a ram readThis is where you twiddle the address, data, nCAS, and nRAS lines to make a RAM read happen
void ramWrite(u32 addr, const u8* buf, u8 sz)Do a ram writeThis is where you twiddle the address, data, nCAS, nWE, and nRAS lines to make a RAM write happen
void __vector_13()Do a ram refreshThis is where you twiddle the nCAS and nRAS lines to make a RAM refresh happen...4096 times in a row. In an AVR this is the interrupt handler, on your board you may choose to do it differently. Just make sure it happens every 62ms

RAM and you

RAM port setup: address lines should all be outputs for you always. nCAS, nRAS, nWE as well. Data lines should be *inputs* by default.

Since AVR asm may not be your language of choice (it certainly is not mine), I'll explain what happens in those functions here. Nah... I am too lazy to explain. I'll just give you C code, and a warning. Actually, warning first: if you can, use ASM and not C - these functions really do need to be fast. They have the potential to slow down your emulation quite a bit, so put some work into them.

General idea here is: ram refresh needs to happen every 64ms. We play it safe and schedule an interrupt every 62ms to happen, where we do the refresh. The refresh can and will break an ongoing read or write. How do we handle it? We disable the refresh interrupt while we're reading/writing. When we finish, we re-enable the interrupt, and if it was queued, it will fire and a refresh will happen right there and then. If your microcontroller of choice does not remember masked interrupts(all the ones I know do), figure out some other way to handle this.

In these mock implementations I make use of "fake" functions like nop, ints_off, ints_on, output_on_address_lines, output_on_data_lines, read_data_lines, make_data_lines_outputs, make_data_lines_inputs. These are just placeholders for your code to do these things.

ramRefresh(interrupt handler calls this)

void ramRefresh(){
	unsigned short cycles = 4096;
	
	do{
		nCAS = 0;
		nRAS = 0;
		nCAS = 1;
		nRAS = 1;
	}while(--cycles);
}

ramRead

void ramRead(u32 addr, u8* buf, u8 sz){
	
	unsigned short row_addr, col_addr;
	
	//step1: prepare address values
	row_addr = addr >> 12;
	col_addr = addr & 0xFFF;
	
	//step 2: disable interrupts
	ints_off();
	
	//step 3: read
	output_on_address_lines(row_addr);
	nRAS = 0;
	while(sz--){
		output_on_address_lines(col_addr++);
		nCAS = 0;
		nop();	//wait just a bit (longer for
			// longer wires, output drivers
			//on this ram stick are VERY weak)
		*buf++ = read_data_lines();
		nCAS = 1;
	}
	nRAS = 1;
	
	//step 4: re-enable interrupts
	ints_on();
}

ramWrite

void ramWrite(u32 addr, const u8* buf, u8 sz){
	
	unsigned short row_addr, col_addr;
	unsigned char val;
	
	//step1: prepare address values
	row_addr = addr >> 12;
	col_addr = addr & 0xFFF;
	
	//step 2: disable interrupts
	ints_off();
	
	//step 3: toggle nWE & make data lines outputs
	nWE = 0;
	make_data_lines_outputs();
	
	//step 4: write
	output_on_address_lines(row_addr);
	nRAS = 0;
	
	while(sz--){
	
		output_on_data_lines(*buf++);
		output_on_address_lines(col_addr++);
		nCAS = 0;
		nop();
		nCAS = 1;
	}
	
	nRAS = 1;
	
	//step 5: fix data lines to normal state & toggle nWE
	make_data_lines_inputs();
	nWE = 1;
	
	//step 6: re-enable interrupts
	ints_on();
}