This is a Chicken Scheme 5 library which lets you operate a AVR microcontroller using its on-board debugWIRE facility. It implements parts of the proprietary serial protocol, and exposes an API that allows you to:
- Start and stop device CPU
- Read and write device registers and SRAM
- Read and write device flash memory
- Execute arbitrary assembly instructions (16-bit opcodes only)
It’s based off of the wonderful work of RikusW, David C W Brown and Rafael G. Martins. The typical use of debugWIRE is indirectly through gdb, where the aforementioned projects shine. This library, however, takes a slightly different approach and tries to give you a Scheme REPL experience when working with these microcontrollers.
⚠ The project stage is experimental, at best! And only the ATtiny85 microcontroller is currently supported. If you actually just want to start with debugWIRE, I’d strongly recommend the projects above.
You’ll need to enable debugWIRE on your target device. This means
programming the DWEN bit, which you’ll probably have to do with the
usual ICSP setup and then something like:
> avrdude -p attiny85 -U hfuse:w:0x9F:mOnce the target is debugWIRE-enabled, the RESET pin becomes available
for debugWIRE commands. Pulling this line low for a few microseconds
should give you the famous 0x55 greeting:
On can pull the line low manually, and then check for a 55 response on a scope before fighting UART drivers and baud rates. This fixed response can be used to detect the target baud rate (usually F_CPU / 64).
A prolonged low signal like this is an invalid serial frame. It’s
called a break, hence the red ? on my scope. It’s used to,
unsurprisingly, break the running target CPU. Once stopped, you can
issue various commands like reading registers and executing ad-hoc
instructions. This is where this library comes into play.
Please follow the hardware setup guide at dwire-debug for the UART setup. You’ll only need a good USB-UART adapter and a diode (plus something to enable debugWIRE). Your adapter needs to be able to generate a proper break signal and baudrates that match the target.
> git clone chicken-debugwire && cd chicken-debugwire
> chicken-install .
> rlwrap csi
;; let's get ready
#;> (import avr.debugwire avr.asm chicken.blob)
#;> (dw-open! "/dev/ttyUSB1" (/ 4000000 64))
#;> (dw-break!)
dw-break-expect: serial read 85
;; CPU is now halted. let's have a look at register 24:
#;> (r 24)
0
#;> (set! (r 24) 100)
#;> (r 24)
100
#;> inc
#<procedure (inc d)>
#;> (inc 24)
#${8395} ;; <-- machine code / opcode for inc 24
#;> (dw-exec (inc 24)) ;; let's increment r24!
#;> (r 24)
101
#;> (dw-exec (inc 24)) ;; let's repeat this for sheer joy
#;> (r 24)
102Not yet documented, sorry. Please read debugwire.scm. However,
below are some of the getter-with-setter procedures. They expose
various named variables of different sizes for convenience. They’re
typically used like this: (r 0) or (set! (r 0) 255) to access
r0.
| name | size | where | description |
|---|---|---|---|
| (PC) | 8 | dw | aka Program Counter. Used/overwritten in various occations. Read immediately after dw-break! to get the “real” PC. |
| (BP) | 8 | dw | aka Break-point. Used/overwritten in various places. Set to flash address before dw-continue* for “run to cursor”. |
| (IR) | 8 | dw | aka Instruction register. Holds an arbitrary 16-bit opcode for immediate execution |
| (r x) | 8 | register | raw register access, where x is 0 - 31. |
| (X) | 16 | register | (r 26) and (r 27) |
| (Y) | 16 | register | (r 28) and (r 29) |
| (Z) | 16 | register | (r 30) and (r 31) |
| (SP) | 16 | sram | aka Stack Pointer. |
| (PINB) | 8 | sram | Port B Input Pins Address (#x16) |
| (DDRB) | 8 | sram | Port B Data Direction Register (#x17) |
| (PORTB) | 8 | sram | Port B Data Register (#x18) |
This list would be seriously long for this library to turn into something of real-world value. So here are just a few major points:
We not only mutate the debugWIRE registers like PC and BP, but we also mutate the on-target Z register when issuing sram-read etc. Therefore, we should keep track of the original values and restore them on dw-continue.
All assembly instructions just take in integers. We should probably
introduce types here. It’s useful to know that an address is of type
flash, we wouldn’t have to manually by two when working with rjmp
etc.
It’s be cool to be able to write C code the usual way, and have access
to it’s symbol table from the REPL. This is in danger of
reimplementing a little too much of gdb, though.
