avr8js

repository·main·Indexed 21 days ago

https://github.com/wokwi/avr8js

A JavaScript library that implements the AVR 8-bit CPU architecture, serving as the core engine for the Wokwi Arduino simulator. It provides instruction execution logic for pre-compiled AVR machine code and runs in the browser or Node.js. While primarily focused on the ATmega328p, it also supports ATmega2560 and the ATtiny series. The library includes components for simulating CPU cores, interrupts, clock events, memory-mapped I/O via hooks, and peripherals such as ADC, SPI, USART, and timers.

Tokens
9.9K
Snippets
38
Records
46
Agent score
74%

What's inside avr8js

  1. Understand the AVR8js architecture and usage model

    main

    AVR8js implements the AVR 8-bit CPU core. It is not a full hardware simulator out of the box; it only simulates the processor itself. To build a complete simulator (like an Arduino simulator), you must provide the following components:

    1. Pre-compiled machine code: The binary instructions for the AVR chip.
    2. Glue code: Logic that connects the AVR8js CPU to your functional simulations.
    3. Functional hardware simulation: Code that simulates the behavior of external components (e.g., LEDs, buttons, sensors).
    4. Simulation state display: A UI to show the user the current state of the simulated hardware.

    Conceptual Workflow: Pre-Compiled machine code $\rightarrow$ AVR8js $\leftrightarrow$ Glue code $\leftrightarrow$ external hardware functional simulation $\leftrightarrow$ simulation state display for the user

    For visual representations of hardware, you can use the wokwi-elements collection of web-components, but note that these are visual-only and require you to implement the functional simulation and glue code yourself.

  2. Explore AVR8js implementation examples

    main

    Since AVR8js only provides the CPU core, several unofficial examples demonstrate how to implement the 'glue code' and 'functional simulation' layers for various components.

    Key examples include:

    • Minimal Setup: Minimal Example
    • Peripherals: 6 LEDs, LED PWM, NeoPixel Matrix, and Serial Monitor.
    • Complex Logic: Simon Game (pushbuttons and sound) and Assembly Code execution.
    • Persistence: EEPROM persistence using LocalStorage.
  3. Run the AVR8js Demo project

    main

    The AVR8js Demo combines AVR8js with the Monaco Editor. You can run the local development environment by following these steps:

    1. Install the necessary dependencies using npm.
    2. Start the development server.
    3. Open your browser to http://localhost:3000/ to interact with the demo.

    The demo environment is configured with hot-reloading, so the application will automatically reload whenever you modify the source code.

    npm install
    npm start
  4. Understand Timer Waveform Generation Modes (WGM)

    main

    The behavior of an AVRTimer is determined by its Waveform Generation Mode (WGM), which is configured via the TCCRA and TCCRB registers. The AVRTimer class uses internal lookup tables (wgmModes8Bit and wgmModes16Bit) to map these bits to specific behaviors.

    Supported TimerModes

    • Normal: Standard free-running counter.
    • CTC (Clear Timer on Compare Match): Resets TCNT to 0 when it matches OCRA or ICR.
    • FastPWM: Fast PWM mode with specific update behaviors for OCR registers.
    • PWMPhaseCorrect: Phase-correct PWM mode where the counter counts up and then down.
    • PWMPhaseFrequencyCorrect: PWM mode used for generating specific frequencies.

    Update Modes

    • OCRUpdateMode: Determines when Output Compare registers (OCRA, OCRB, etc.) are updated (Immediate, Top, or Bottom).
    • TOVUpdateMode: Determines when the Timer Overflow flag is set (Max, Top, or Bottom).
  5. Understand AVRWatchdog register behavior

    main

    The AVRWatchdog class simulates the behavior of the AVR Watchdog Timer, including its protection mechanism.

    Watchdog Change Enable (WDCE)

    To prevent accidental changes to the watchdog configuration, the WDTCSR register is protected. Changes to the WDE (Watchdog Enable) or WDP (Prescaler) bits are only permitted if the WDCE bit is set. In the simulation, writing to WDTCSR with the WDCE bit active enables a window of 4 CPU cycles during which the protection mask can be bypassed to update configuration bits.

  6. Configure GPIO Port Interrupts

    main

    AVR8js supports both External Interrupts and Pin Change Interrupts (PCINT) via the AVRPortConfig interface. This allows the simulator to trigger CPU interrupts when specific pin transitions occur.

    External Interrupts

    Defined via the externalInterrupts array in AVRPortConfig. Each entry maps to an AVRExternalInterrupt object which specifies the registers involved in the interrupt lifecycle:

    • EICR: Interrupt Control Register (holds configuration bits).
    • EIMSK: External Interrupt Mask Register (enables/disables interrupts).
    • EIFR: External Interrupt Flag Register.
    • index: The bit index in EIMSK and EIFR.
    • interrupt: The interrupt vector address.

    Pin Change Interrupts

    Defined via the pinChange property in AVRPortConfig using the AVRPinChangeInterrupt interface. This covers groups of pins that trigger an interrupt on any logical change:

    • PCICR: Pin Change Interrupt Control Register.
    • PCIFR: Pin Change Interrupt Flag Register.
    • PCMSK: Pin Change Mask Register.
  7. Configure ADC input types and channels

    main

    The ADC peripheral uses ADCMuxInput to define how different channels behave. When configuring the ADC via muxChannels in the ADCConfig, you can use the following input types:

    • ADCMuxInputType.SingleEnded: Reads a voltage from a specific channel.
    • ADCMuxInputType.Differential: Reads the difference between a positiveChannel and a negativeChannel, multiplied by a gain.
    • ADCMuxInputType.Constant: Returns a fixed voltage.
    • ADCMuxInputType.Temperature: Returns a fixed voltage representing 25°C (0.378125V).

    ADCMuxConfiguration is a map where keys are channel numbers and values are ADCMuxInput objects.

    import { ADCMuxInputType } from './src/peripherals/adc';
    
    const myMuxConfig = {
      0: { type: ADCMuxInputType.SingleEnded, channel: 0 },
      1: { type: ADCMuxInputType.Differential, positiveChannel: 2, negativeChannel: 3, gain: 1 },
      2: { type: ADCMuxInputType.Constant, voltage: 1.1 },
      3: { type: ADCMuxInputType.Temperature }
    };
  8. Configure the ADC via ADCConfig

    main

    The ADCConfig interface defines the register map and configuration for an AVRADC instance. Use this to map the peripheral to specific CPU memory addresses and define the available channels.

    Key fields include:

    • ADMUX, ADCSRA, ADCSRB, ADCL, ADCH, DIDR0: Register addresses.
    • adcInterrupt: The address for the ADC interrupt.
    • numChannels: Total number of channels available.
    • muxInputMask: Mask used to extract the channel from the ADMUX register.
    • muxChannels: A mapping of channel numbers to ADCMuxInput configurations.
    • adcReferences: An array of supported ADCReference types.
  9. Configure the AVRSPI peripheral

    main

    When instantiating AVRSPI, you must provide an SPIConfig object that maps the peripheral to specific AVR register addresses.

    Required keys in SPIConfig:

    • spiInterrupt: The interrupt vector address.
    • SPCR: SPI Control Register address.
    • SPSR: SPI Status Register address.
    • SPDR: SPI Data Register address.

    A default configuration is provided in spiConfig for standard AVR chips.

    import { SPIConfig, spiConfig } from './src/peripherals/spi';
    
    const myConfig: SPIConfig = {
      spiInterrupt: 0x22,
      SPCR: 0x4c,
      SPSR: 0x4d,
      SPDR: 0x4e,
    };
  10. Configure the AVR Watchdog Timer

    main

    To use the Watchdog Timer peripheral in an AVR simulation, you must provide a WatchdogConfig object that maps the peripheral's registers to specific memory addresses. The configuration requires addresses for the watchdog interrupt, the MCUSR (MCU Status Register), and the WDTCSR (Watchdog Timer Control and Status Register).

    export interface WatchdogConfig {
      watchdogInterrupt: u8;
      MCUSR: u8;
      WDTCSR: u8;
    }
    
    // Default configuration used by AVR8js
    export const watchdogConfig: WatchdogConfig = {
      watchdogInterrupt: 0x0c,
      MCUSR: 0x54,
      WDTCSR: 0x60,
    };
  11. Configure the AVREEPROM peripheral

    main

    The AVREEPROM class can be customized using the AVREEPROMConfig interface. This allows you to define the register addresses, interrupt configuration, and the timing for erase and write operations. If no configuration is provided, it defaults to eepromConfig.

    import { AVREEPROM, eepromConfig, AVREEPROMConfig } from './src/peripherals/eeprom';
    
    const customConfig: AVREEPROMConfig = {
      eepromReadyInterrupt: 0x2c,
      EECR: 0x3f,
      EEDR: 0x40,
      EEARL: 0x41,
      EEARH: 0x42,
      eraseCycles: 28800,
      writeCycles: 28800,
    };
    
    // Usage in constructor
    // new AVREEPROM(cpu, backend, customConfig);
  12. Configure the USART peripheral with USARTConfig

    main

    To instantiate an AVRUSART instance, you must provide a USARTConfig object that maps the peripheral's registers and interrupt addresses to the CPU's memory map.

    Commonly, you can use the predefined usart0Config for standard ATmega-style USART implementations.

    USARTConfig requires the following keys:

    • rxCompleteInterrupt: Address for the RX Complete interrupt.
    • dataRegisterEmptyInterrupt: Address for the Data Register Empty interrupt.
    • txCompleteInterrupt: Address for the TX Complete interrupt.
    • UCSRA: Address of the USART Control and Status Register A.
    • UCSRB: Address of the USART Control and Status Register B.
    • UCSRC: Address of the USART Control and Status Register C.
    • UBRRL: Address of the USART Baud Rate Register Low.
    • UBRRH: Address of the USART Baud Rate Register High.
    • UDR: Address of the USART Data Register.
    import { AVRUSART, usart0Config } from './peripherals/usart';
    
    // Assuming 'cpu' is an existing CPU instance
    const usart = new AVRUSART(cpu, usart0Config, 16000000);