wokwi / avr8js

Arduino (8-bit AVR) simulator, written in JavaScript and runs in the browser / Node.js
https://blog.wokwi.com/avr8js-simulate-arduino-in-javascript/
MIT License
463 stars 73 forks source link

Implement EEPROM peripheral #15

Closed urish closed 3 years ago

urish commented 4 years ago

EEPROM allows the AVR program to persist data between runs. The implementation will probably allow the user to specify a callback to read/write/erase data, thus supporting different backends. For instance, the user could use localStorage as a backend for the EEPROM data.

Register description can be found in page 31 / section 8.6 of the datasheet.

yiivon commented 3 years ago

Please consider the following draft, I have done a simple test

eeprom.ts

import {CPU} from '../cpu/cpu';
import {avrInterrupt} from '../cpu/interrupt';
import {u8} from '../types';

export interface EEPROMConfig {
    EECR: u8;
    EEDR: u8;
    EEARL: u8;
    EEARH: u8;
}

export const eepromConfig: EEPROMConfig = {
    EECR: 0x3F,
    EEDR: 0x40,
    EEARL: 0x41,
    EEARH: 0x42,
};

const EERE = 1 << 0;
const EEPE = 1 << 1;
const EEMPE = 1 << 2;
const EERIE = 1 << 3;
const EEPM0 = 1 << 4;
const EEPM1 = 1 << 5;

const PREFIX = 'EEPROM';

export class EEPROM {
    constructor(private cpu: CPU, private config: EEPROMConfig) {
        this.cpu.data[this.config.EEARH] &= 1;
        this.cpu.data[this.config.EEDR] = 0;
        this.cpu.data[this.config.EECR] &= 0b00110010;

        this.cpu.writeHooks[this.config.EECR] = (eecr, ov) => {

            let al = this.cpu.data[this.config.EEARL];
            let ah = this.cpu.data[this.config.EEARH];

            let addr = (ah<<8) + al;
            let eedr = this.cpu.data[this.config.EEDR];

            // read
            if(eecr & EERE) {
                let r = localStorage.getItem(PREFIX + `[${addr}]`);
                this.cpu.data[this.config.EEDR] = r;
                return true;
            }
            // write
            else if(eecr & EEPE) {
                // Erase + Write
                if(!(eecr & EEPM0) && !(eecr & EEPM1)) {
                    localStorage.setItem(PREFIX + `[${addr}]`, eedr);
                }
                // Erase only
                else if((eecr & EEPM0) && !(eecr && EEPM1)) {
                    localStorage.removeItem(PREFIX + `[${addr}]`);
                }
                // Write only
                else if(!(eecr & EEPM0) && (eecr && EEPM1)) {
                    let r = localStorage.getItem(PREFIX + `[${addr}]`);
                    localStorage.setItem(PREFIX + `[${addr}]`, eedr | r);
                }
                // rev.
                else {
                }

                this.cpu.data[this.config.EECR] &= ~EEPE;
                return true;
            }

            // default handling
            return false;
        };
    }

    tick() {
        if (this.cpu.interruptsEnabled) {
        }
    }
}
urish commented 3 years ago

Thank you so much!

Do you happen to know some AVR assembly as well? It will be great to have test cases in assembly that run can also run on the actual chip and compare the implementation, like we have for TWI or for the timers.

yiivon commented 3 years ago

Thanks for your good suggestions.

Most of my time is spent on PC development, and only a small amount of time is spent on chip development, so I am not very familiar with avr assembly and may need time to learn it.

Avr8js is a good project and excellent idea, I am trying to run grbl hex based on it. So the code I wrote directly runs grbl for testing and executes it correctly to achieve my goal.

However, in the process of running grbl, an additional problem arises: maybe there is some implementation of timer1 or timer0 that is not very perfect, because the interrupt function of pwm implemented by timer1 or timer0 in grbl has not been executed, including CTC mode. The way grbl uses timers may be special, but it works correctly on physical chips and proteus8.

For the problem of timer, I am seriously analyzing the chip manual and reading the source code of your relevant part, i try to find the difference between the avr8js timer implemented and physical chip. I also hope to get your help.

Thank you!

urish commented 3 years ago

Hi @yiivon, thank you for sharing information about your project!

The timers should be pretty accurate, but there is at least one known issue: #41.

It would be best if we could find a simple code example that reproduces the problem, as this would make finding the issue much easier.

Also, how did you set up the timers?

urish commented 3 years ago

Thanks again @yiivon, I implemented this feature by the simulator inspired by your code, also with tests.

In my implementation, you pass an EEPROMBackend, which is the object the manages the actual storage for the EEPROM. There's also sample backend which stores everything in memory is provided, called EEPROMMemoryBackend, and it should be pretty straight forward to implement something that stores the values to localStorage, like you did above.

urish commented 3 years ago

Here is an example of EEPROMBackend that is saved to localStorage, similar to what you did:

https://stackblitz.com/edit/avr8js-eeprom-localstorage?file=eeprom-localstorage-backend.ts