Home > Atmel AVR > Primitive Virtual Machine for AVR

Primitive Virtual Machine for AVR

A tiny bytecode interpreter with serial programming support
By 28/12/10 [Last Edited by Joseph 28/12/10]
BOOKMARK
LOGIN
REGISTER
This is very poorly written, and very very simplistic, owing to the fact it was just a small thing to see if it could work. There is no stack, no registers for storage, and very few opcodes. Aside from that, it did work, and very basic PIC functionality could be implemented! Programming could be performed by sending a CMD_WRITE_EEPROM byte, the desired EEPROM address, then the value. That could be done in a loop to program the whole block of ROM; yes, terribly inefficient I know. Also, only half of the ROM could be addressed, I didn't get round to implementing the code to address over an 8-bit word by squashing two bytes together by using endianness.

Compiled using AVR Studio 4 targeting an ATMega8515

// Include library files
#include <avr/io.h>
#include <stdlib.h>
#define F_CPU 3680000UL //Define crystal/RC osc frequency
#include <util/delay.h>
#include <avr/wdt.h>
#include <avr/interrupt.h>
#include <avr/eeprom.h>

//Definitions
#define CMD_PING 3
#define CMD_READ_EEPROM 4
#define CMD_WRITE_EEPROM 5

#define ACK_OK 100
#define ACK_ERROR 99

// Prototypes
void init_usart(unsigned int baud);
void init_io(void);
void txbyte(unsigned char data);
unsigned char rxbyte(void);
void startup_tasks(void);
void out_debug(unsigned char output);
void out(unsigned char output);
unsigned char out_state(void);
void machine(void);

// Let's go

int main(void) 
{
	startup_tasks(); //Boot procedure for microcontroller
	while(1)
	{ //Infinite loop, let ISRs do all the hard work from now on
		machine();
	}
}

void machine()
{
	unsigned char pc = 1;
	unsigned char acc = 0;
	unsigned char opc;

	while(pc != 256)
	{
		opc = (unsigned char)eeprom_read_byte((uint8_t*)pc);
		pc++;
		switch(opc)
		{
			case 2: //Output
				out(acc);
				break;
			case 1: //Load Accumulator
				acc = (unsigned char)eeprom_read_byte((uint8_t*)pc + 1);
				pc++;
				break;
			case 3: //Increment
				acc++;
				break;
			case 4: //Decrement
				acc--;
				break;
			case 5: //Delay
				_delay_ms(1000);
				break;
			case 6: //Jump
				pc = (unsigned char)eeprom_read_byte((uint8_t*)pc + 1);
				break;
			case 7: //Jump on zero
				if (acc == 0) 
				{
					pc = (unsigned char)eeprom_read_byte((uint8_t*)pc + 1);
				}
				else
				{
					pc++;
				}
				break;
			case 8: //Jump on non-zero
				if (acc > 0) 
				{
					pc = (unsigned char)eeprom_read_byte((uint8_t*)pc + 1);
				}
				else
				{
					pc++;
				}
				break;
			case 9: //Add x to accumulator
				acc += (unsigned char)eeprom_read_byte((uint8_t*)pc + 1);
				pc++;
				break;
			case 10: //Sub x from acc
				acc -= (unsigned char)eeprom_read_byte((uint8_t*)pc + 1);
				pc++;
				break;		

		}
	}
}

void startup_tasks(void) 
{
	init_usart(23); //Init the USART (Serial) for 3.68Mhz clock at 9600 Baud
	init_io();
	sei(); //Enable interrupts
}

void out_debug(unsigned char output) 
{
	//Procedure to output byte to 'debug' port
	PORTA = output;
}

void out(unsigned char output) 
{
	//Procedure to output byte to output port
	PORTB = output;
}

unsigned char out_state(void) 
{
	//Procedure to get the current output state
	return PORTB;
}

void init_usart(unsigned int baud) 
{
	// Setup serial I/O for communication
	UBRRH = (unsigned char)(baud>>8);
	UBRRL = (unsigned char)baud;                           
	UCSRB = (1 << RXEN) | (1 << TXEN) | (1 << RXCIE);
	UCSRC = (1 << URSEL) | (3 << UCSZ0);
}

SIGNAL (SIG_UART_RECV) 
{ 
	//USART RX interrupt service routine
	switch(UDR) 
	{
		case(CMD_PING):
			txbyte(ACK_OK);
			break;
		case CMD_READ_EEPROM:
			txbyte((unsigned char)eeprom_read_byte((uint8_t*)rxbyte()));
			break;
		case CMD_WRITE_EEPROM:
			eeprom_write_byte ((uint8_t*)rxbyte(), (uint8_t)rxbyte());
			txbyte(ACK_OK);
			break;
		default:
			txbyte(ACK_ERROR);
			break;
	}
}

void init_io(void) 
{
	DDRB = 255; 
	DDRA = 255;
}

void txbyte(unsigned char data) 
{
	while ( !( UCSRA & (1<<UDRE)) ); //Wait for empty slot to send data
	UDR = data; //Send it
}

unsigned char rxbyte (void) 
{
	while ( !(UCSRA & (1<<RXC)) ); //Wait until there is a byte to be read from RX buffer
	return UDR; //Get it
}