Coder Social home page Coder Social logo

wokwi / avr8js Goto Github PK

View Code? Open in Web Editor NEW
464.0 16.0 75.0 2.92 MB

Arduino (8-bit AVR) simulator, written in JavaScript and runs in the browser / Node.js

Home Page: https://blog.wokwi.com/avr8js-simulate-arduino-in-javascript/

License: MIT License

JavaScript 0.10% TypeScript 99.89% Shell 0.02%
avr simulation arduino atmega atmega328p javascript typescript microcontroller

avr8js's Introduction

AVR8js

This is a JavaScript library that implementats the AVR 8-bit architecture.

It's the heart- but not the whole body- of the Arduino simulator at https://wokwi.com.

Build Status NPM Version License: MIT Types: TypeScript Gitpod ready-to-code

Example Applications Using AVR8js

How to Use This Library

This library only implements the AVR CPU core. You have to supply it pre-compiled machine code to run, and implement functional simulations of any external hardware. You will probably also want to add audio/visual representations of external hardware being simulated.

A rough conceptual diagram:

Pre-Compiled machine code --> AVR8js <--> Glue code <--> external hardware functional simulation <--> simulation state display for the user

You may be interested in exploring the wokwi-elements collection of web-components for visual representations of many common hardware components. (Note: these are visual only elements- you will need to add the appropriate functional simulation and glue code.)

Walkthrough Video Tutorial

A step-by-step video tutorial showing how to build a simple Arduino simulator using AVR8js and React:

AVR8JS Walkthrough Video

And a related blog post.

Unofficial examples

These examples show working examples of using avr8js in an application. Many of them also demonstrate how to use the wokwi-elements and include working examples of functional simulations of the components, and how to hook them up to avr8js.

Note: they are all hosted outside of this repo.

Running the demo project

The demo project allows you to edit Arduino code, compile it, and run it in the simulator. It also simulates 2 LEDs connected to pins 12 and 13 (PB4 and PB5).

To run the demo project, check out this repository, run npm install and then npm start.

Which chips can be simulated?

The library focuses on simulating the ATmega328p, which is the MCU used by the Arduino Uno.

However, the code is built in a modular way, and is highly configurable, making it possible to simulate many chips from the AVR8 family, such as the ATmega2560 and the ATtiny series:

Check out issue 67 and issue 73 for more information.

Running the tests

Run the tests once:

npm test

Run the tests of the files you modified since last commit (watch mode):

npm run test:watch

For more information, please check the Contributing Guide.

License

Copyright (C) 2019-2023 Uri Shaked. The code is released under the terms of the MIT license.

avr8js's People

Contributors

dependabot[bot] avatar dudeplayz avatar gfeun avatar julianrendell avatar lironhazan avatar urish avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

avr8js's Issues

Suggestion - project structure by feature while keeping a LIFT practice

It will draw a better picture (especially when scaling up) if the project entities will be structured as features, and utils/services will be located per feature (preserving LIFT)

LIFT stands for:
Locate code quickly, Identify the code at a glance, keep the Flattest structure you can, and Try to be DRY

I suggest to start by locating the peripherals classes under:
/peripherals

Missing ATmega2560 instructions

The following instructions are used to reference memory addresses beyond 0x10000 (for MCUs with more than 128kb program space), and are currently missing from the simulation:

  • EICALL
  • EIJMP
  • ELPM

Also, CALL, ICALL, RCALL, RET, and RETI should be adapted for 22-bit PC

Attaching pushbutton input to PIN register

Hello again !

So I have made some progress on my virtual training board using avr8js.
I have 8 leds + 8 pushbutton.
Using demo project it was quite easy to set up.

I am now wondering if there is a "nice" way to hook up the pushbuttons to a specific µController GPIO port.

What I am doing now is register some event listener on the push-button events. On event, I access cpu register directly and modify the internal cpu data field of the corresponding register

function buttonPress(e :Event) {
  const id = e.target.id.slice(-1)
  const bitMask = 1 << id;
  runner.cpu.data[runner.portC.portConfig.PIN] = runner.cpu.data[runner.portC.portConfig.PIN] | bitMask
}

function buttonRelease(e :Event) {
  const id = e.target.id.slice(-1)
  const bitMask = ~(1 << id);
  runner.cpu.data[runner.portC.portConfig.PIN] = runner.cpu.data[runner.portC.portConfig.PIN] & bitMask
}

// buttons ids are "button1", "button2"
const buttonModule = new Array<PushButtonElement>(8);
for(let i=0; i<buttonModule.length; i++) {
  buttonModule[i] = document.querySelector<PushButtonElement>("wokwi-pushbutton[id=button"+i+"]");
  buttonModule[i].addEventListener("button-press", buttonPress, false)
  buttonModule[i].addEventListener("button-release", buttonRelease, false)
}

This is working, with the following code i have a "blink led according to button pressed":

setup() {
 DDRB = 0xFF;
 DDRC = 0x00;
}
loop() {
 delay(500);
 PORTB = PINC;
 delay(500);
 PORTB = 0;
}

But i find modifying the internal cpu data directly looks like a hack and i may have missed the "proper" way to do it. Do you have any recommendation or good practice on how to do it ?

Thank you !

Improve interrupt handling

Currently each peripheral implements part of the interrupt handling logic, such as checking for interrupt condition, and clearing the corresponding interrupt flag after the. This creates some code duplication, does not respect interrupt priority, and also allows multiple interrupt handler to run sequentially.

Section 7.7 of the datasheet describes how interrupts should work:

  1. The list also determines the priority levels of the different interrupts. The lower the address the higher is the priority level. RESET has the highest priority, and next is INT0 – the External Interrupt Request 0.
  2. When the AVR exits from an interrupt, it will always return to the main program and execute one more instruction before any pending interrupt is served.

In other words, RETI should disable interrupts for one CPU instruction.

Implement EEPROM peripheral

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.

usart.ts code typo?

src/peripherals/usart.ts line 103
if (ucsrb & UCSRA_TXC && ucsrb & UCSRB_TXCIE) {
is ucsra & UCSRA_TXC ?

Implement Compare Match Output for timers

The Compare Match Output bits are used to generate hardware PWM signals on selected MCU pins. This is also the mechanism used by Arduino's analogWrite method.

From the datasheet (page 113, section 15.9.1):

These bits control the Output Compare pin (OC0A) behavior. If one or both of the COM0A1:0 bits are set, the OC0A output overrides the normal port functionality of the I/O pin it is connected to. However, note that the Data Direction Register (DDR) bit corresponding to the OC0A pin must be set in order to enable the output driver.

Some hints and questions

Hello Uri,
Dario here, we have written per mail some weeks ago. I had now the time to take a look at the project. I have found some small questions and maybe hints I want to discuss here:

1 - Would it be useful to allow multiple interrupt hooks for the same address? In this case, you can, for example, attach a logger for specific addresses or something like that.

2 - In the cpu.ts the hook is called and checked for a boolean. I have seen that this is used for gpio and if the hook returns true, the write is not saved into memory. Can you tell me, why you have designed it this way?

3 - Some places in the code use number, but you have declared some types for smaller ones, which are mapped to number. I think this places in code should be refactored to use the correct reference instead of number to make it clear which type is expected to make it more compatible with future additions and environments. For example here the number can be replaced with u16 (uint16):

avr8js/src/cpu/cpu.ts

Lines 81 to 83 in f34f825

set SP(value: number) {
this.dataView.setUint16(93, value, true);
}

4 - A similar case is, that the return types are not declared explicitly, which is not necessary in typescript but makes it more clear what is expected and new contributors can understand it better.

5 -

avr8js/src/cpu/cpu.ts

Lines 89 to 91 in f34f825

get interruptsEnabled() {
return this.SREG & 0x80 ? true : false;
}

Could be refactored to:

get interruptsEnabled() {
    return !!(this.SREG & 0x80);
}

(Just a hint of my IDE don't know if it speed up anything)

6 - Code points like:

cpu.cycles++;

Which are called very often could be refactored to ++cpu.cycles to cause a slightly faster operation. See StackOverflow: old link, but found also some newer articles about it.

7 - The next question is, how works the command decoding at the moment? I've seen the big if-else block and my mind tells me to refactor it 😄, but I think I've seen another issue about this already? Something like a look-up table would be much faster, but in this case, I think the whole decoding must be refactored. For the moment a smaller speed up would be to take some heuristics and reorder the if-else cases so that the commands which are called more often are "found" faster on the top.
8 -

this.writeGpio(value & portValue, oldValue & oldValue);

Has the oldValue & oldValue some effects? It looks for me like a copy&paste with forgotten refactor 😄.

9 -

get bitsPerChar() {
const ucsz =
((this.cpu.data[this.config.UCSRA] & (UCSRC_UCSZ1 | UCSRC_UCSZ0)) >> 1) |
(this.cpu.data[this.config.UCSRB] & UCSRB_UCSZ2);
switch (ucsz) {
case 0:
return 5;
case 1:
return 6;
case 2:
return 7;
case 3:
return 8;
default: // 4..6 are reserved
case 7:
return 9;
}
}

Is it correct that case 7 applies to the default case (4 .. 6)? Haven't dug through the code or the sheets to know the effect of this, so it's only a question.

So thank you for reading until this point. If something is helpful it can be put in separate issues 😀.

Have a nice day!

Calling `pinState()` inside a GPIO port listener returns incorrect values after changing DDR

Reproduction test case:

it('should reflect the current port state when called inside a listener after DDR change', () => {
  const cpu = new CPU(new Uint16Array(1024));
  const port = new AVRIOPort(cpu, portBConfig);
  const listener = jest.fn(() => {
    expect(port.pinState(0)).toBe(PinState.Low);
  });
  expect(port.pinState(0)).toBe(PinState.Input);
  port.addListener(listener);
  cpu.writeData(0x24, 0x01); // DDRB <- 0x01
  expect(listener).toHaveBeenCalled();
});

Editor UX - Save user history to localStorage on emulation run

It will improve development experience if we'll save the cpp code every time the user hits "run simulation".

I suggest adding a tiny util which abstracts (safely) the localStorage.addItem and localStorage.removeItem.
state machine:
check if there's a localstorage entry:
yes: remove it and add a new one.
No: add one.

** see TS playground as a reference.

Performance bottleneck due to setTimeout(fn, 0)

I think I identified a potential performance bottleneck due to the way task queing works in JS event loop

Disclaimer, I'm not familiar with JS internals, just compiling some search results

This setTimeout(fn, 0) is potentially "rate limited" by browsers :

await new Promise((resolve) => setTimeout(resolve, 0));

From https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout

In modern browsers, setTimeout()/setInterval() calls are throttled to a minimum of once every 4 ms when successive calls are triggered due to callback nesting (where the nesting level is at least a certain depth), or after certain number of successive intervals.

While it states that this applies to "recursive" setTimeout calls, it is not clear to me if it applies to consecutive setTimeout calls. It may explain performance differences between Chrome and Firefox because they may handle this differently.

The "recommended" workaround I found is to use the window.postMessage API to schedule next tasks.

This old posts (maybe not relevant anymore) details the throttling happening:
https://dbaron.org/log/20100309-faster-timeouts

And the associated test page gives me better performances for the postMessage (setZeroTimeout) method (https://dbaron.org/mozilla/zero-timeout)

100 iterations of setZeroTimeout took 12 milliseconds.
100 iterations of setTimeout(0) took 215 milliseconds.

This SO answer: https://stackoverflow.com/questions/18826570/settimeout0-vs-window-postmessage-vs-messageport-postmessage with a corresponding jsfiddle http://jsfiddle.net/Noseratio/wPCN4/6/ points in the same direction

2000 iterations of setTimeoutPM took 124 milliseconds.
2000 iterations of setTimeoutMC took 67 milliseconds.
2000 iterations of setTimeout(0) took 5187 milliseconds.

NodeJS demo example ?!

First, congratulations on the project.
I ended up not finding a pure nodejs version of the examples. and created the repository:
https://github.com/ricardojlrufino/avr8js_demo_node

I am thinking of some way of integrating with an application made in JAVA, a plugin for arduino IDE.
Java supports JavaScript code execution, but I believe it will be slow.
My first attempt was this and it got really slow: using: graalvm / graaljs
oracle/graaljs#296

16 bit TIMER1 doesn't work

Took me a while to figure this out.

Interrupts are triggered here:

if (
(this.WGM === WGM_NORMAL ||
this.WGM === WGM_PWM_PHASE_CORRECT ||
this.WGM === WGM_FASTPWM) &&
val > newVal
) {
this.TIFR |= TOV;
}

The this.WGM is implement here:

get WGM() {
return ((this.TCCRB & 0x8) >> 1) | (this.TCCRA & 0x3);
}

The timer1 is managed a bit differently than the 8 bit timers 0 and 2.
The WGM is 4 bits with : WG11/WG10 in bits 1/0 of TCCR1A and WG13/WG12 in bits 4/3 of TCCR1B

See pages 140 and 142 of http://ww1.microchip.com/downloads/en/DeviceDoc/ATmega48A-PA-88A-PA-168A-PA-328-P-DS-DS40002061A.pdf

We should implement timer 0/2 and timer 1 differently

EICALL instruction is broken

According to the datasheet, the EICALL instruction should push a 3-byte return address to the stack, but currently it only pushes 2. This breaks ATmega2560 simulation.

Incorrect memory address for RAMPZ / EIND (affects ATmega2560)

Currently, the code assumes the RAMPZ is at 0x3b, and EIND is at 0x3c. These are the I/O address space addresses of the registers. However, we are using the data address space to address these registers. This means we need to access RAMPZ at 0x5b, and EIND at 0x5c.

These issue only affect devices with >64KB of flash memory, such as the ATmega2560. It affects the following instructions:

  • EIJMP
  • ELPM
  • EICALL

These instructions were introduced by #31.

Optimize CPU busy waits

There are many cases where the Arduino code is simply waiting for an external event. One example is calling delay(), which is implemented as a loop that repeatedly looks at the value of micros(). Another example is Serial.write, which waits for an interrupt or an external flag to be set when the TX buffer is full.

The basic idea would be to detect any loop that has no side effects (i.e.it only reads from the memory), and skip all the iterations of the loop until the next external event (timer firing / interrupt).

There are several factors that makes the implementation challenging:

  1. Many instructions update the status register (SREG).
  2. In many cases, the busy-waiting code does some calculations, and thus it has a local side effect of updating register values.
  3. We still need to keep the clock cycle count correct, as if we actually executed the skipped instructions.
  4. Currently, the avrInstruction() method advances the clock cycle count, and other peripherals (e.g. timers) synchronize with it by looking at the value of cpu.cycles. We will need to implement a new mechanism to keep track of the clock cycles and to synchronize the different peripherals to it.

As an example, here is the disassembly of the Arduino delay() function (as compiled by the Arduino CLI). It was obtained by compiling the "Blink" program and then running avr-objdump -S on the generated ELF file:

 1de:   0e 94 b8 00     call    0x170   ; 0x170 <micros>
 1e2:   68 19           sub     r22, r8
 1e4:   79 09           sbc     r23, r9
 1e6:   8a 09           sbc     r24, r10
 1e8:   9b 09           sbc     r25, r11
 1ea:   68 3e           cpi     r22, 0xE8       ; 232
 1ec:   73 40           sbci    r23, 0x03       ; 3
 1ee:   81 05           cpc     r24, r1
 1f0:   91 05           cpc     r25, r1
 1f2:   a8 f3           brcs    .-22            ; 0x1de <delay.constprop.1+0x24>

As you can see here, the code does some calculations that updates the registers r22 - r25, as well as the status register, and it also calls the micros() function. The call instruction pushes a value to the stack and updates the stack pointer, causing another temporary side effect (that is later cancelled out when we call ret in micros()).

And the disassembly of micros():

unsigned long micros() {
        unsigned long m;
        uint8_t oldSREG = SREG, t;
 170:   3f b7           in      r19, 0x3f       ; 63

        cli();
 172:   f8 94           cli
        m = timer0_overflow_count;
 174:   80 91 05 01     lds     r24, 0x0105     ; 0x800105 <timer0_overflow_count>
 178:   90 91 06 01     lds     r25, 0x0106     ; 0x800106 <timer0_overflow_count+0x1>
 17c:   a0 91 07 01     lds     r26, 0x0107     ; 0x800107 <timer0_overflow_count+0x2>
 180:   b0 91 08 01     lds     r27, 0x0108     ; 0x800108 <timer0_overflow_count+0x3>
        t = TCNT0;
 184:   26 b5           in      r18, 0x26       ; 38
        if ((TIFR0 & _BV(TOV0)) && (t < 255))
 186:   a8 9b           sbis    0x15, 0 ; 21
 188:   05 c0           rjmp    .+10            ; 0x194 <micros+0x24>
 18a:   2f 3f           cpi     r18, 0xFF       ; 255
 18c:   19 f0           breq    .+6             ; 0x194 <micros+0x24>
                m++;
 18e:   01 96           adiw    r24, 0x01       ; 1
 190:   a1 1d           adc     r26, r1
 192:   b1 1d           adc     r27, r1
        if ((TIFR & _BV(TOV0)) && (t < 255))
                m++;
        SREG = oldSREG;
 194:   3f bf           out     0x3f, r19       ; 63

        return ((m << 8) + t) * (64 / clockCyclesPerMicrosecond());
 196:   ba 2f           mov     r27, r26
 198:   a9 2f           mov     r26, r25
 19a:   98 2f           mov     r25, r24
 19c:   88 27           eor     r24, r24
 19e:   bc 01           movw    r22, r24
 1a0:   cd 01           movw    r24, r26
 1a2:   62 0f           add     r22, r18
 1a4:   71 1d           adc     r23, r1
 1a6:   81 1d           adc     r24, r1
 1a8:   91 1d           adc     r25, r1
 1aa:   42 e0           ldi     r20, 0x02       ; 2
 1ac:   66 0f           add     r22, r22
 1ae:   77 1f           adc     r23, r23
 1b0:   88 1f           adc     r24, r24
 1b2:   99 1f           adc     r25, r25
 1b4:   4a 95           dec     r20
 1b6:   d1 f7           brne    .-12            ; 0x1ac <micros+0x3c>
}
 1b8:   08 95           ret

This code also changes the status register (through the in and out instructions, and as a result of the arithmetic instructions), as well as registers r18, r20, and r22-r27.

Wrong prescaler for Timer2

Currently all timers share the same prescaler table. However, has a slightly different list of prescalers. From instance, settings CS22:0 to 6 should result in 256 prescaling instead of an using an external clock source.

Advice on simulating SD card with SPI data protocol

Hi @urish
I am planning to simulate an SD card to store data from simulated sensors.
The goal is to have the actual storage be in a database for each student (which is the storage of the SD card) and then they'll be able to load that data in a python environment to do data analysis. I would like to have a proper use of the SD.h and SPI.h libraries.

I just started looking into this but if anyone has any advice or comments I'd appreciate it.
For example can I use SPI communication protocol with the AVR8js?

SS – digital 10. You can use other digital pins, but 10 is generally the default as it is next to the other SPI pins;
MOSI – digital 11;
MISO – digital 12;
SCK – digital 13;

Avr8js does not to run grbl firmware completely accurately.

In fact, I am very eager for avr8js to run grbl firmware completely and accurately.

GRBL is a firmware implemented in pure C language running on arduino uno (ATmega328P).

I use the latest version of Proteus to run grbl for arduino simulation, and I can get the correct result. But the current avr8js does not seem to be able to achieve the same, I guess it may be that some implementations of timer are not perfect.

If you also want to make avr8js run like real chips, you can try to run grbl firmware in emulation, which may solve this problem and make avr8js close to real chips again. At the same time, it also helped me a lot.

Ask for your help and thank you sincerely!

Originally posted by @yiivon in #11 (comment)

AssemblyScript conflicts

This issue collects conflicts of the current code base and AssemblyScript. See #35 and the research project.

Found problems:

  1. Data types must be specified explicitly
  2. AssemblyScript has a restricted data type set
    • The current types file can be adapted to keep JS compatibility
  3. Interfaces are currently not supported. Breaks e.g. ICPU and CPU. (further research required)
  4. Declaring variables as constructor parameters brings up memory access errors because the instantiation works differently. E.g. CPU with progMem, which has to be declared outside the constructor and the following instantiations have to be done in the constructor body:
    constructor(progMem: Uint16Array, private sramBytes: u64 = 8192) {
        this.progMem = progMem;
        this.progBytes = Uint8Array.wrap(this.progMem.buffer);
        this.pc22Bits = this.progBytes.length > 0x20000;
        this.reset();
    }

(The research is still ongoing.)

incorrect 16-bit timer high counter byte (TCNT1H) behavior

According to the datasheet, the value of the high byte of the counter for 16-bit timers (such as timer 1) is only updated when the low byte is being read/written (page 126 of the datasheet):

The TCNT1H Register can only be indirectly accessed by the CPU. When the CPU does an access to the TCNT1H I/O location, the CPU accesses the high byte temporary register (TEMP). The temporary register is updated with the TCNT1H value when the TCNT1L is read, and TCNT1H is updated with the temporary register value when TCNT1L is written.

Currently, writing to TCNT1H updates the counter immediately.

Timing issues

Hi @urish I'm running the code below, and I noticed that the millis() function goes crazy after running for a while, approximately 1 ~ 2 min, it happened in my tests with Electron and in the online simulation.

Have any idea what it could be? Thanks

tested in the environment: https://wokwi.com/arduino/libraries/demo/blink

diagram.json

{
  "version": 1,
  "author": "Anderson Costa",
  "editor": "wokwi",
  "parts": [
    { "id": "uno", "type": "wokwi-arduino-uno", "top": 120, "left": 20 },
    { "id": "r1", "type": "wokwi-resistor", "top": 67, "left": 80, "rotate": 90, "attrs": {"value": "220"} },
    { "id": "l1", "type": "wokwi-led", "left": 80, "top": 0, "attrs": { "color": "red" }},
    { "id": "r2", "type": "wokwi-resistor", "top": 67, "left": 115, "rotate": 90, "attrs": {"value": "220"} },
    { "id": "l2", "type": "wokwi-led", "left": 120, "top": 0, "attrs": { "color": "green" }},
    { "id": "r3", "type": "wokwi-resistor", "top": 67, "left": 163, "rotate": 90, "attrs": {"value": "220"} },
    { "id": "l3", "type": "wokwi-led", "left": 160, "top": 0, "attrs": { "color": "blue" }}
  ],
  "connections": [
    ["uno:GND.1", "l1:C", "black", []],
    ["uno:GND.1", "l2:C", "black", []],
    ["uno:GND.1", "l3:C", "black", []],
    ["r1:1", "l1:A", "red", []],
    ["r2:1", "l2:A", "green", []],
    ["r3:1", "l3:A", "blue", []],
    ["uno:13", "r1:2", "red", []],
    ["uno:11", "r2:2", "green", []],
    ["uno:8", "r3:2", "blue", []]
  ]
}

blink.ino

#define DELAY_LED 1000

// LEDs connected to pins...
byte ledPins[] = {13, 11, 8};
byte i = 0;

unsigned int timerLed = 0;

bool switchLed = true;

// The setup function runs once when you press reset or power the board
void setup() {
  // Initialize digital pins LED as an output.
  for (byte n = 0; n < sizeof(ledPins); n++) {
    pinMode(ledPins[n], OUTPUT);
  }
}

// The loop function runs over and over again forever
void loop() {
  if ((millis() - timerLed) > DELAY_LED) {
    timerLed = millis();

    digitalWrite(ledPins[i], switchLed);

    if ((i + 1) == sizeof(ledPins)) {
      switchLed = !switchLed;
    }

    i = (i + 1) % sizeof(ledPins);
  }
}

Serial port (USART TX) should respect the given baud rate

In the current implementation, whenever a byte is sent through the Serial port (USART), we immediately acknowledge it. In practice, sending data through the AVR Serial port takes time. For instance, if we use 9600 baud rate, we can send about 960 bytes per second (each byte requires 10 bits in total: 1 start bit, 8 data bits, and 1 stop bit). Thus, we should only mark the transfer completed after ~1.04ms (or ~16667 clock cycles when running in 16MHz).

In other words, when running the following program:

void setup() {
  Serial.begin(9600);
}

void loop() {
  Serial.println("Hello, Serial!");
}

We should get about 60 lines printed every second ("Hello, Serial!\r\n" is 16 bytes, and 960/16 == 60).

When calculating the delay before setting the TX Complete flag after each byte transmission, we ideally need to take the following parameters into account:

  • The baud rate (we already calculate it in the baudRate getter)
  • The number of bits per character (see bitsPerChar getter)
  • The presence of a parity bit (check the datasheet for more info)
  • The number of stop bits (1 or 2)

In practice, most implementations only change the baud rate (9600 and 115200 are probably the most common values), and the other parameters are static (8 bits per bytes, no parity, 1 stop bit), so we can decide to start only with the baud rate.

Information about the USART registers can be found in page 200 / section 20.11 of the datasheet.

Create a CONTRIBUTING guide

A document that explains about the project structure, the tools that we use, how to build it, and how to contribute. Some of the things we'd like to mention:

  1. Commit messages - we use conventional commits.
  2. License - the contributed code is released under the MIT license.
  3. Tooling - we use eslint and prettier to keep the code clean, and editorconfig to configure the indent in a cross-editor fashion.
  4. Recommended code editor - Visual Studio code is preferred, and we have an extensions.json, so when you open the project in VSCode, it will automatically prompt to install recommended extensions.
  5. Recommended reading material - Datasheets, etc
  6. Tests: we use jest for the unit-tests, and we aim to get a high coverage of the core library. It's recommended to use Wallaby.js to run the tests in the IDE and also track the code coverage.

How to trigger writehooks at a specific microsecond efficiently?

Hi Uri,
I am simulating the ultrasonic sensor. I need to trigger an event at a specific microsecond.
For the ultrasonic sensor, I need to trigger the pin LOW after a specific microsecond duration.

In my Arduino class, I have events that are triggered with periods. see here

The events are being handled inside the this.runner.execute function call. (I don't know if it is the best way to trigger such events)

I lowered the workUnitCycles in execute.ts to have the execute callback be triggered at microseconds, but that made the speed extremely slow.

You can find the source code here
And you can check the working environment here

Right now the ultrasonic sensor is triggered to return constant distance (200cm). Because the call back of the writehook is not accurate to microseconds, the pin is not triggered low on time. Which is resulting in inaccurate pulseIn return value.

Implement execution throttling when over 100%

A great issue to write 😄

So now that the simulation runs faster than the simulated CPU, it would be nice to have the possibility to throttle at 100% speed

Similar to how application target a specific number of Frame Per Second, we could target CPU Freq instructions per second, not more.

This would be implemented in the execute runner function.

Not a priority but nice to have.

Timers keeps counting even when stopped

Use the following code to reproduce:

int main() {
  TCCR2B = 1 << CS20;
  byte value = TCNT2;
  Serial.begin(115200);
  Serial.println(value);
}

On a physical ATmega328p this code prints 1, as the counter only starts counting after we set the prescaler in the first line of main(). In the simulation, however, the same code prints 22, as the timer is counting even before the prescaler is set.

Arduino Mega 2560

Would love to see an Arduino Mega 2560 version! I hit the Uno memory limits pretty quickly using FastLED, so the extra memory would be super helpful.

Add more GPIO ports

Currently, GPIO ports B, C, and D are implemented. Some devices, such as the ATmega2560, have many more GPIO ports: A, B, C, D, E, F, G, H, J, K, and L. Therefore, we want to include configuration objects for all the above ports (A-K).

Add support for AssemblyScript

Hey Uri,
We have already spoken about the possibilities to speed up the simulation. I am interested in adding support for AssemblyScript. I followed the recent discussions and speed ups. The question is if you still think it can bring some performance gains, also after the enhancements that were made?

Reading TCNT in 2-cycle instructions

When reading TCNT using a 2-cycle instruction (such as LDS), the simulation behaves differently than the silicone. The following code demonstrates the issue:

void setup() {
  unsigned int value = 0;
  cli();
  TCCR2A = 0; 
  TCCR2B = 1 << CS10; 

  /* See the generated assembly code below */
  TCNT2 = 0;
  asm("nop");
  value = TCNT2;

  sei();
  Serial.begin(115200);
  Serial.print("Clock cycles: ");
  Serial.println(value - 1);
}
 
// the loop function runs over and over again forever
void loop() {
}

Running this code in the simulation prints 1, while running it on a physical ATmega328p prints 2.

If you inspect the generated assembly code, you will see that it uses STS to set the value of TCNT2, then executes the NOP instruction, followed by an LDS to read the value of TCNT2:

STS 0x00B2, r1
NOP
LDS r12, 0x00B2

The LDS instruction takes 2 cycles to execute, and it seems like the counter is still incremented on the first cycle, before being copied into the register during the second cycle.

Timer value should not increment on the same cycle as TCNTn write

According to the datasheet: "A CPU write overrides (has priority over)
all counter clear or count operations".

Currently, writing to the TCNTn register modifies its value, and then the tick() function runs and immediately checks (and possibly updates) the new value.

This creates a discrepancy between the simulation and the real AVR core, and can be demonstrated by the following program:

int main() {
  int value = 0;
  cli();
  TCCR0A = 0; 
  TCCR0B = 1 << CS00; 
  TCNT0 = 0;
  // TCNT0 should not increment in this clock cycle
  value = TCNT0;
  sei();
  Serial.begin(115200);
  Serial.print(value);
}

Running this code in the simulation prints "1", while running it on an Arduino Uno board prints "0".

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.