๐Ÿ‡ฎ๐Ÿ‡น IT ๐Ÿ‡ฌ๐Ÿ‡ง EN

๐Ÿ”Œ Communicate with Arduino via Browser
HTML + JavaScript + Web Serial API

For absolute beginners. Step-by-step explanation with commented code.

๐ŸŽฅ Video: Serial communication with Arduino

From browser to Arduino: open the port, send commands, read responses. With '1' turn LED on, with '0' turn it off.

๐Ÿ“ฆ for beginners

What is Web Serial API?

Web Serial API is a browser feature (Chrome, Edge) that allows a web page to talk directly to serial devices like Arduino, ESP32, GRBL boards, etc.

No plugins, no external software. Just HTML and JavaScript.

๐Ÿ”Œ What you need
  • A computer with Chrome or Edge
  • Arduino (or any serial board) connected via USB
  • This page (or a copy)
step 1

Try it now

Connect Arduino with the program in the "What happens inside Arduino" section. Then click "Connect" and choose the right port.

Try: send '1' (turns LED on D13 on), send '0' (turns LED off). Arduino always echoes back the same character.

step 2

JavaScript explanation for beginners

let port, writer, reader โ€” three variables we'll use to talk to Arduino. They're like three empty boxes.

async function connect() โ€” does all the opening work:

async function disconnect() โ€” closes everything when you're done

async function sendData() โ€” takes the text you wrote and sends it to Arduino

๐Ÿ’ก Try modifying: change baudRate to 9600 if your Arduino uses that speed.

step 3

Commented JavaScript code

// ๐Ÿ”ง GLOBAL VARIABLES let port; // the serial port when opened let writer; // to write data to Arduino let reader; // to read data from Arduino let keepReading = true; // switch: if false stops reading // ๐Ÿ”Œ CONNECT async function connect() { try { // Asks user to select a serial port port = await navigator.serial.requestPort(); // Opens port at 115200 baud (must match Arduino) await port.open({ baudRate: 115200 }); // Prepares writing and reading channels writer = port.writable.getWriter(); reader = port.readable.getReader(); keepReading = true; // Infinite loop waiting for messages from Arduino while (keepReading) { const { value, done } = await reader.read(); // wait for message if (done) break; // if port closes, exit if (value) { // Converts bytes to readable text let text = new TextDecoder().decode(value); const output = document.getElementById("output"); output.value += text; // append to box output.scrollTop = output.scrollHeight; // scroll down } } } catch (err) { alert("Error: " + err); } } // โŒ DISCONNECT async function disconnect() { keepReading = false; // stops reading loop if (reader) { await reader.cancel(); reader.releaseLock(); } if (writer) writer.releaseLock(); if (port) await port.close(); document.getElementById("output").value += "\n** Connection closed **\n"; } // ๐Ÿ“ค SEND DATA async function sendData() { const text = document.getElementById("textToSend").value; if (writer && text) { // Converts to bytes and adds \r\n (newline) const data = new TextEncoder().encode(text + "\r\n"); await writer.write(data); document.getElementById("textToSend").value = ""; // clear input } }
extra

What happens inside Arduino when a byte arrives

The browser sends bytes. Inside Arduino, these are the registers that activate:

RegisterAddressBitNameWhat it does
UBRR0L0xC47-0UBRR[7:0]baud rate low byte
UBRR0H0xC57-0UBRR[15:8]baud rate high byte
UCSR0B0xC14RXEN0=1 to enable reception
UCSR0B0xC13TXEN0=1 to enable transmission
UCSR0C0xC22-1UCSZ0[1:0]=11 for 8 data bits
UCSR0A0xC07RXC0=1 when a byte arrived
UCSR0A0xC05UDRE0=1 when transmission buffer empty
UDR00xC67-0UDRreceived byte (or to transmit)
DDRB0x045DDB5=1 to set pin D13 as OUTPUT
PORTB0x055PORTB5=1 to turn LED on (5V), =0 to turn off (0V)

Initial configuration (done once):

; Set baud rate 9600 (UBRR = 103 for 16MHz) ldi r16, 0x00 sts 0xC5, r16 ; UBRR0H = 0 ldi r16, 0x67 ; 103 = 0x67 sts 0xC4, r16 ; UBRR0L = 0x67 ; Enable reception and transmission (UCSR0B) ldi r16, 0b00011000 ; bit4=RXEN0, bit3=TXEN0 sts 0xC1, r16 ; UCSR0B = 0x18 ; 8 bit format (UCSR0C) ldi r16, 0b00000110 ; bit2=UCSZ01, bit1=UCSZ00 sts 0xC2, r16 ; UCSR0C = 0x06 ; Set pin D13 as OUTPUT (DDRB bit5 = 1) ldi r16, 0b00100000 ; only bit5 = 1 out 0x04, r16 ; DDRB = 0x20

When the browser SENDS a byte:

; wait for a byte to arrive (RXC0 = 1) wait_rx: lds r16, 0xC0 ; read UCSR0A (0xC0) sbrs r16, 7 ; skip if bit7 = 1 (data arrived) rjmp wait_rx ; otherwise try again ; read the received byte from UDR0 (0xC6) lds r17, 0xC6 ; r17 = character sent by browser ; compare with '1' (0x31 in ASCII) cpi r17, '1' ; is it '1'? brne check_0 ; if not, check if it's '0' sbi 0x05, 5 ; turn LED on (PORTB bit5 = 1) rjmp reply check_0: cpi r17, '0' ; is it '0' (0x30 in ASCII)? brne reply ; if not, skip cbi 0x05, 5 ; turn LED off (PORTB bit5 = 0)

When Arduino REPLIES (sends a byte to browser):

; wait for transmission buffer to be empty (UDRE0 = 1) reply: wait_tx: lds r16, 0xC0 ; read UCSR0A (0xC0) sbrs r16, 5 ; skip if bit5 = 1 (buffer empty) rjmp wait_tx ; otherwise wait ; send back the same byte (echo) sts 0xC6, r17 ; UDR0 = received byte rjmp loop
๐Ÿ“ฆ Mailbox metaphor

UDR0 (0xC6) is the mailbox.
UCSR0A bit7 (RXC0) is the "mail arrived" flag.
UCSR0A bit5 (UDRE0) is the "outgoing mailbox empty" flag.
PORTB bit5 (0x05.5) is the LED switch.

๐Ÿ“ฅ Complete program to upload to Arduino:
Receives bytes, turns LED on if '1', off if '0', and always echoes back the character.

With CostyCNC online assembler uploader: https://www.costycnc.it/avr1/compiler.html

; ------------------------------------------------------------ ; UART + LED - receives, controls LED, and echoes back ; ------------------------------------------------------------ .org 0 rjmp init .org 0x68 init: ; Serial configuration ldi r16, 0x00 sts 0xC5, r16 ; UBRR0H = 0 ldi r16, 0x67 sts 0xC4, r16 ; UBRR0L = 103 ldi r16, 0b00011000 sts 0xC1, r16 ; UCSR0B = RXEN0 + TXEN0 ldi r16, 0b00000110 sts 0xC2, r16 ; UCSR0C = 8 bit ; Configure LED on D13 (PB5) as OUTPUT ldi r16, 0b00100000 out 0x04, r16 ; DDRB = 0x20 loop: ; Wait for incoming byte wait_rx: lds r16, 0xC0 sbrs r16, 7 rjmp wait_rx ; Read received byte lds r17, 0xC6 ; Check if it's '1' (turn LED on) cpi r17, '1' brne check_0 sbi 0x05, 5 ; PORTB bit5 = 1 (LED ON) rjmp reply check_0: cpi r17, '0' brne reply cbi 0x05, 5 ; PORTB bit5 = 0 (LED OFF) reply: ; Wait for transmission buffer empty wait_tx: lds r16, 0xC0 sbrs r16, 5 rjmp wait_tx ; Echo back the same byte sts 0xC6, r17 rjmp loop
๐ŸŽ›๏ธ the truth

What you learn NOW on Arduino reveals how EVERYTHING works

๐Ÿ‹๏ธ ARDUINO IS YOUR GYM

Writing directly to registers on a tiny ATmega328 is NOT a useless nostalgic exercise. It's EXACTLY how the most complex systems on the planet work.

What you see here with UDR0 (0xC6) and PORTB (0x05) is the exact same logic used by:

๐Ÿ’ป GRAPHICS CARDS (GPU)

NVIDIA RTX 5090, AMD Radeon: thousands of registers. The driver writes to addresses like 0x3C0 to set resolution, 0x3C4 for frequency. Exactly the same thing: an address, a value, and the hardware reacts.

๐Ÿ“Œ Your sbi 0x05,5 = NVIDIA driver writing to 0x3C0

๐ŸŽง AUDIO CARDS

Realtek, Sound Blaster: hundreds of registers for volume, EQ. Writing 0x50 to register 0x2A = 50% volume. Identical to your sts 0xC6, r17.

๐Ÿ“Œ Your sts 0xC6, r17 = audio driver writing to 0x2A

๐Ÿš— CAR ECUs

Bosch, Denso: thousands of registers via OBD2. Read 0x0C = RPM, 0x0D = speed. Like you reading UCSR0A.

๐Ÿ“Œ Your lds r16, 0xC0 = OBD2 scanner reading 0x0C

๐Ÿ”Œ PCI EXPRESS

NVMe, USB, network cards: memory-mapped registers. The driver writes to 0xF0008000. Same philosophy as your out 0x04, r16.

๐Ÿ“Œ Your out 0x04, r16 = NVMe driver writing to 0xF0008000

๐Ÿง  MODERN CPUs

Intel i9, Apple M4, STM32, ESP32: all have registers. Names change, principle is identical. You already know 90% of how to program any micro.

๐Ÿ“Œ sbi 0x05,5 = GPIOB->BSRR on STM32

๐Ÿ–ฅ๏ธ CHIPSET

Intel Z790, AMD B650: manage USB, SATA, PCIe via registers. BIOS writes to 0xCF8/0xCFC. Same logic.

๐Ÿ“Œ Your sts 0xC6, r17 = BIOS writing to 0xCFC

๐Ÿ”ฅ THE FINAL REVELATION ๐Ÿ”ฅ

๐Ÿ–ฅ๏ธ

RTX 5090

0x3C0

=
๐ŸŽง

Realtek

0x2A

=
๐Ÿš—

BMW ECU

0x0C

โฌ‡๏ธ
๐Ÿ› ๏ธ

ARDUINO

0xC6 (UDR0) ยท 0x05 (PORTB)

๐Ÿ“ข IT'S THE EXACT SAME THING!

๐Ÿง  THIS IS THE POINT

When you learn to write sts 0xC6, r17 on Arduino, you're not learning something old and useless.

You're learning how the ENTIRE DIGITAL WORLD works.

  • Your โ‚ฌ2000 PC graphics card works like this
  • Your car's ECU works like this
  • The microcontroller in your refrigerator works like this
  • Your phone's processor works like this
  • Your recording studio's audio interface works like this
  • Supercomputers work like this

โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
THERE IS NO MAGIC. ONLY NUMBERS IN REGISTERS.
โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

Arduino is not a toy. It's the gym where you learn the rules that govern ALL hardware.

๐Ÿ“ Test yourself

Question: When a byte arrives from the browser, which bit rises and in which register? And to turn the LED on, which register and bit do you use?

Show answer

Bit RXC0 (bit 7) in register UCSR0A (address 0xC0) indicates a byte arrived. To turn the LED on you use PORTB (0x05) bit 5 = 1.