aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore4
-rw-r--r--LICENSE14
-rw-r--r--Makefile36
-rw-r--r--README.md107
-rw-r--r--guides/debugging.md81
-rw-r--r--timer.s110
6 files changed, 352 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..26734b8
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+*.hex
+*.o
+*.elf
+timer
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..193cf48
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,14 @@
+BSD Zero Clause License
+
+Copyright (c) 2025 Sotov Konstantin
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THIS SOFTWARE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..6b8f6b4
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,36 @@
+# adjust according to your needs
+AS = avr-as
+LD = avr-ld
+OBJ = avr-obj
+MCU ?= atmega328p
+MCUPATH ?= /dev/ttyACM0
+DUDE ?= avrdude
+PROGRAMMER ?= arduino
+LDFLAGS += -m avr5
+DUDEFLAGS += -c $(PROGRAMMER) -p $(MCU) -P $(MCUPATH)
+
+# timer.s file
+TARGET ?= timer
+
+.PHONY: flash dump-timer clean all
+
+all: $(TARGET).hex
+
+flash: $(TARGET).hex
+ test $(shell id -u) = 0
+ $(DUDE) $(DUDEFLAGS) -U flash:w:$<
+
+%.hex: %
+ $(OBJ)copy -O ihex $< $@
+
+%: %.o
+ $(LD) $(LDFLAGS) -o $@ $<
+
+%.o: %.s
+ $(AS) -mmcu=$(MCU) -o $@ $<
+
+dump-%: %
+ $(OBJ)dump -d timer
+
+clean:
+ rm *.o *.hex *.elf *.bin timer
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..575493b
--- /dev/null
+++ b/README.md
@@ -0,0 +1,107 @@
+# AVR assembly programming
+This is a repo containing my attempts at programming in AVR assembly
+
+I want the every byte of macihne code executed by my processor to be written by
+myself, so no libraries, no definitions, no nothing.
+
+### DISCLAIMER
+Please have a sense of humor and don't take the jokes in this
+README seriously.
+
+### WARNING
+ChatGPT and any other genAI, as of 2025-09-05, can NOT write proper
+AVR assembly. And I doubt they'll learn any time soon. Try it for yourself,
+maybe you'll get something working if they write it completely from scratch,
+but as for debugging, you are on your own. Use debugging tools mentioned below.
+
+Examples of generative AI being completely useless include: them trying to
+persuade me that I've used the wrong interrupt vector address, mixing up
+`out`/`sts` and `in`/`lds` instructions, mixing up addresses of IO registers,
+writing/reading data from wrong registers (like mixing up `TIMSK`/`TIFR`,
+`PINx/PORTx`), not knowing how to actually compile the code and so much more,
+not unserstanding how timers work and so on. Save yourself some time, don't use
+genAI.
+
+## How can I use it?
+Firstly, as an example of how to program in assembly for
+AVR. I am not responsible for the quality though :silly:. My only source is
+datasheets.
+
+Secondly, there is a really useful [Makefile](Makefile) that you can copy to use
+with your own projects. Most variables do not need to be changed there, maybe
+just the `MCUPATH`. Name a file `something.s`, and then run `make
+TARGET=something` to build, or `make TARGET=something flash` to upload your
+code to the arduino. You can set the `TARGET` as an environment variable to
+aviod typing it in each invokation of `make`.
+
+Thirdly, for debugging. Run `make TARGET=something sim`, then run `avr-gdb
+something`, when inside of it, type `target remote :1234` and you're good to go.
+`make sim` automatically detects wheher the simulation is already running and
+forks to background, so to rerun the simulation, just type `make sim` again: it
+will rebuild the file, and you won't even have to restart gdb (it will preserve
+breakpoints), just retype the `target remote :1234` command. I find this
+workflow satisfying, and it does not exhaust my arduino flash memory by
+flashing the program every 30 seconds there. I think I will add a debugging
+guide as well...
+
+## Dependencies
+### Software
+* `make`
+* avr toolchain (`avr-as`, `avr-ld`, `avr-objcopy`, `avr-objdump`)
+* `avrdude` (flash tool)
+* For debugging: `avr-gdb` + `simavr` (or `qemu-system-avr`, but I had problems
+ with it)
+
+Not that you don't actually need `avr-libc` or `avr-gcc` to build assembly.
+### Hardware
+* an ARDUINO UNO board, or any other AVR-based microcontroller if you change
+ the variables in Makefile. Note that most of the variables can be changed
+ from the environment.
+* wirez
+* rizz
+
+### Dependency installation
+* Arch linux: `# pacman -S avr-binutils avrdude`. For C development, you also
+ need to install `avr-gcc` and `avr-libc`. For debugging, `avr-gdb` and
+ `simavr` [\[AUR\]](https://aur.archlinux.org/packages/simavr)
+ [\[CLONE\]](https://aur.archlinux.org/simavr.git)
+* Any other non-superior distro: read the fucking manual
+
+## Why?
+- to have fun.
+
+## Useful resources
+* [ATMega328p datasheet](https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-7810-Automotive-Microcontrollers-ATmega328P_Datasheet.pdf)
+* [AVR instruction manual](https://ww1.microchip.com/downloads/en/DeviceDoc/AVR-Instruction-Set-Manual-DS40002198A.pdf)
+* [AVR130: Setup and use of AVR Timers](https://ww1.microchip.com/downloads/en/Appnotes/Atmel-2505-Setup-and-Use-of-AVR-Timers_ApplicationNote_AVR130.pdf)
+* [Arduino UNO R3 info](https://docs.arduino.cc/hardware/uno-rev3/), [PINOUT](https://docs.arduino.cc/resources/pinouts/A000066-full-pinout.pdf)
+
+## Read this if you are going into it blind
+This section contains some of the pain that I've gone through while doing this
+programming journey. If you don't want to make same mistakes, please take your
+time to read it.
+
+0. AVR interrupt addresses from datasheet are not the same ones you specify in
+ assembly! <br>
+ For example, TIMER1 COMPA (timer 1 compare A match) has address
+ `0x0016` in the datasheet, but in assembly you have to align it like this:
+ ```asm
+ .org 0x002C: jmp timer1_int
+ ```
+ Why? Because they probably refer to "instruction address", not to "memory
+ address". Is it said anywhere? No. Can you figure it out by compiling
+ corresponding C code and looking at disassembly? Yes, but you will spend 5
+ hours trying to understand why your interrupt is not working.
+0. Don't forget to link your file after compiling! <br>
+ This would have not been a problem on a processor that has an OS, because
+ the file just wouldn't run unless you link it. But here you're using
+ `objcopy`. That means that ALL jumps and calls will be broken in your
+ assembly. I thought that this was a bug for so long, dont be like me.
+0. I've said this before, but don't ever try to use AI, or even worse, vibe
+ code the microcontroller. It's just not going to work, you will loose time
+ instead of saving it. You can vibecode a crappy website in react only because
+ there is so much training data for it. But almost entirety of AVR programming
+ is done in C++, thanks to fucking nobody. But I mean, it is a good thing that
+ AI can't comprehend assembly. It means you'll have more fun exploring the old
+ fashined way ;)
+0. I will add entries to this list as I go...
diff --git a/guides/debugging.md b/guides/debugging.md
new file mode 100644
index 0000000..1e13191
--- /dev/null
+++ b/guides/debugging.md
@@ -0,0 +1,81 @@
+# This is a simple guide to using AVR-GDB with simavr
+First of all, you can use the [Makefile](../Makefile) in the directory above.
+But for the competeness sake, I'll list commands
+
+## Running simulation
+QEMU: I don't personally prefer it, but here is a breakdown:
+```sh
+qemu-system-avr -M arduino-uno -bios <image> -nographic -s -S
+```
+* `-M`: machine to use, shorthand for `--machine`. Self-explanatory.
+* `-bios`: the thing that you would flash onto your arduino. Note that this is
+ not a hex file, this must be an elf file, the one you get out of `ld`.
+* `-nographic`: this should be default to be honest
+* `-s`: shorthand for `-gdb tcp::1234`. Enables GDB debugging
+* `-S`: suspend execution and wait for debugger to connect.
+This should be everything you need.
+[More documentation] (https://www.qemu.org/docs/master/system/target-avr.html)
+
+SIMAVR: This is the one I prefer
+
+First of all, the `--help` is actually not as big so it's easy to guess
+everything yourself, but fine, here is the command:
+```sh
+simavr -m atmega328p -g <image>
+```
+I think you can already see why I like it more. Breakdown of flags:
+* `-m, --mcu` stands for "MicroController Unit". atmega328p is the processor arduino
+ uno uses
+* `-g, --gdb`: wait for gdb to connect on `:1234`
+* `-f, --freq`: optionally specify the clock frequency, arduino uno has 16MHz
+You can also interrupt the process to kill it! Qemu does not let you do that.
+
+## gdb
+```sh
+$ avr-gdb <image>
+(gdb) target remote :1234
+(gdb) c
+```
+First of all, load the image. Then connect to the simulation. Since this is not
+a program in a traditional sense, it's already running and you have to
+`continue` instead of `run`.
+* to break at an address, use `b *0x69`. also, 0x69 is odd, so you will not land
+ there. Instructions are either 16 bit or 32 bit on AVR, so almost any even
+ address is a valid instruction.
+* to print instructions at some address, use `x/16i 0x420`, replace 16 with
+ amount of instructions and 0x420 with an actual address. you can also use
+ `$pc`, which stands for program counter (current instruction) as an address.
+ Also, unlike on intel/arm64, you can safely write `x/4i $pc-4` and get a valid
+ disassembly.
+* To print register values, use `print $r16`, replace `r16` with any register
+ you want. For formatted printing, see `help x` in gdb shell, it is often
+ useful to `print/x` in hexadecimal or `print/t` in binary.
+* use `displ` command. You will probably want to track current instruction, 1 or
+ 2 registers and some IO memory addresses. For me, a "must have" is
+ `displ/i $pc`, which displays an instruction to be executed each time the
+ execution avdances. really useful for stepping
+* `si` command steps 1 assembly insstruction. you will find yourself using it a
+ lot
+* To print IO registers, use `print *(unsigned char*)0xADD12E55`. NOTE that, if
+ you can use `in/out` instruction to access a memory location, gdb can't, it
+ will always use `sts/lds`. So don't forget `0x20` to all IO locations before
+ `0x3F`. For example, if you want to track `PORTB`, you'd often write
+ `displ/t *(unsigned char)0x25`, even though documentation says the `PORTB` is
+ at `0x05`.
+* To restart the program without restarting the simulation, you can write
+ `set $pc = 0`. This will trick a processor into thinking that next instruction
+ is at `0x00`, which is a reset vector. Note that state of your registers does
+ not reset. The set command it useful in many places, for example you want a
+ loop to execute for a little longer - you can set the counter register to 0.
+* you can safely put breakpoints at interrupts, at any labels you defined,
+ anywhere.
+* I coudn't get `compile code` gdb instruction to work (to execute assembly
+ on-the-fly), because it can't find the avr C compiler. Maybe this is a
+ distribution misconfiguration. Didn't bother setting up stuff in a debian VM.
+
+This is it! Hope this was helpful.
+
+P.S.: Internet has a lof of `avr-gdb` guides, and even more "just `gdb`" guides,
+which are as relevant. Search them up and have fun debugging!
+
+JAC, 2025
diff --git a/timer.s b/timer.s
new file mode 100644
index 0000000..94e97ae
--- /dev/null
+++ b/timer.s
@@ -0,0 +1,110 @@
+.equ PINB, 0x03
+.equ DDRB, 0x04
+.equ PORTB, 0x05
+.equ PIND, 0x09
+.equ DDRD, 0x0A
+.equ PORTD, 0x0B
+.equ TIFR0, 0x15
+.equ ADDR, 0x256
+.equ SPL, 0x3D
+.equ SPH, 0x3E
+.equ SREG, 0x3F
+.equ TCCR0A, 0x0024
+.equ TCCR0B, 0x0025
+.equ TCNT0, 0x0026
+.equ OCR0A, 0x0027
+.equ TIMSK0, 0x006E
+.equ STOFL, 0xFF
+.equ STOFH, 0x08
+.equ TOIE0, 1
+
+.equ LOC, 0x128
+.equ FREQ, 0x80
+
+.org 0x0000 ; reset interrupt
+jmp init
+.org 0x0040 ; timer 0 overflow interrupt
+jmp tmrint
+
+
+.org 0x0068
+init:
+ ; IO
+ sbi DDRB, 5
+ sbi DDRD, 2
+ cbi PORTB, 5
+ cbi PORTD, 2
+ ; stack
+ ldi r16, STOFL
+ ldi r17, STOFH
+ out SPL, r16
+ out SPH, r17
+ ; initial
+ ldi r16, 69
+ sts ADDR, r16
+ ; timer
+ ldi r18, 0b00000001
+ out TCCR0B, r18
+ ldi r18, TOIE0
+ sts TIMSK0, r18
+ sei
+ rcall main
+
+tmrint:
+ push r18
+ in r18, SREG
+ push r18
+ push r19
+ push r20
+ push r21
+
+ lds r20, LOC
+ ldi r21, FREQ
+ inc r20
+ cpse r20, r21
+ rjmp .stt
+
+ in r18, PORTD
+ ldi r19, (1 << 2)
+ eor r18, r19
+ out PORTD, r18
+ eor r20, r20
+.stt:
+ sts LOC, r20
+.exit:
+ pop r21
+ pop r20
+ pop r19
+ pop r18
+ out SREG, r18
+ pop r18
+ sei
+ reti
+
+
+main:
+ sei
+ ldi r17, 128
+.loop:
+ lds r1, ADDR
+ in r6,TCNT0
+ cp r1, r17
+ breq .finloop
+ inc r1
+ sts ADDR, r1
+ rjmp .loop
+.finloop:
+
+ in r13, PINB
+ in r14, PORTB
+
+ lds r16, ADDR
+ cpse r16, r17
+ cbi PORTB, 5
+
+ in r13, PINB
+ in r14, PORTB
+
+ rjmp main
+
+