kristianlm / updi-gdbserver

Debug your AVR programs with avr-gdb through UPDI and a UART dongle
2 stars 0 forks source link

+begin_quote

This project is in alpha stage. Only a few ATTiny devices have been tested, but most UPDI-based devices are expected to work. Please open issues if you encounter problems!

+end_quote

~updi-gdbserver~ bridges [[https://sourceware.org/gdb/][avr-gdb]] and you favorite [[https://en.wikipedia.org/wiki/AVR_microcontrollers#UPDI][UPDI]]-enabled AVR microcontroller. It can be used to debug (stop, inspect, modify) and program your target.

: avr-gdb : +------------+ : | | : | TCP | : +-----+------+ : | gdb remote UART : | protocol dongle AVR chip : +-----+----------+ +-------+ +-------------+ : | TCP | | | | | : | (listen) | | TX |---1kOhm--+--+ UPDI pin | : | tty +------+ | | | | : | | | RX |----------+ | | : +----------------+ +-------+ +-------------+ : updi-gdbserver | | : GND GND

** Getting started

*** Building

Unfortunately, this tool is written in an exoteric language and has unusual build steps. Try this:

+begin_src bash

apt install chicken-bin # or pacman -S chicken or something thereof chicken-install -s srfi-18 # install dependencies git clone https://github.com/kristianlm/updi-gdbserver cd updi-gdbserver csc -O1 gdbserver.scm # or chicken-csc ./gdbserver -b Set baudrate of UPDI serial port (required) -p Set TCP listening port for GDB-server (defaults to 4444) -r Set TCP listening port for Chicken Scheme repl (defaults to off) -v Verbose logs to stderr -g Disable initial (SIB) greeting

+end_src

*** Hardware setup

Follow the ordinary [[https://github.com/microchip-pic-avr-tools/pymcuprog#serial-port-updi-pyupdi][pymcuprog]] guidance for how to get the serial port set up to talk UPDI.

Since the UPDI interface resembles debugWIRE (see below), you can also give [[https://github.com/dcwbrown/dwire-debug][dwire-debug]] a read for more details and for some UART adapter recommendations. It can also help to try with ~avrdude~ first, since its ~serialupdi~ programmer uses the same communication mechanisms:

+begin_src bash

avrdude -c serialupdi -P /dev/ttyUSB1 -b 9600 -p t414

+end_src

For the devices that have a separate UPDI-pin, this should be all that's required to use UPDI. Some devices share the UPDI pin and need a special high-voltage sequence to enable UPDI on the target. This project does not support that. Luckily, UPDI has been enabled by default on all devices tested so far. So if you're willing to sacrifice a pin dedicated to UPDI, this simple UART-based debugging experience may be for you.

*** Building target example

If you're lucky enough to have a version of ~avr-gcc~ above 12.0 or 13.0 (and recent accompanying ~avr-libc~), the newer UPDI-enabled AVR devices are supported out of the box:

+begin_src bash

avr-gcc -DF_CPU=1000000 -Os -g3 -mmcu=attiny414 \ example-led-blink.c -o example-led-blink.elf

+end_src

Older ~avr-gcc~ versions can still target the newer UPDI-enabled devices, but you'll need to provide appropriate headers and libraries. https://start.atmel.com/ may be able to provide you with a project template.

The ~-g~ option ensures that debugging information, like symbol names, is available to ~avr-gdb~. The ~-g3~ option also includes preprocessor macros, like ~PORTA~, which is handy to have available in your ~avr-gdb~ session.

Programming your target can be done with the ~load~ command from ~avr-gdb~. Here, ~avr-gdb~ will extract the program section of the elf-file and send that to ~updi-gdbserver~ in a series of ~vFlashWrite~ commands followed by a final ~vFlashDone~. ~updi-gdbserver~ holds the flash pages in memory and writes the modified pages to the target device after receiving ~vFlashDone~. When recompiling your elf-files, running ~load~ is sufficient to both update the (modified pages of) target flash, and update the current ~avr-gdb~ session with any changes.

*** Running ~gdbserver~

Complete the build steps above, then try this, for example:

+begin_src bash

./gdbserver -b 115200 /dev/ttyUSB1 Starting gdbserver on port 4444. Press ctrl-c to quit. Connect with, for example: avr-gdb -ex "target extended-remote 127.0.0.1:4444" example-led-blink.elf

+end_src

*** Running ~avr-gdb~

With ~gdbserver~ running and the stars aligned, you might be able to do something this:

+begin_src sh

avr-gdb --eval-command "target remote 127.0.0.1:4444" example-led-blink.elf GNU gdb (GDB) 12.1 …

program target flash:

(gdb) load Loading section .text, size 0xd4 lma 0x0 Start address 0x00000000, load size 212 Transfer rate: 237 bytes/sec, 42 bytes/write.

(gdb) info b No breakpoints or watchpoints.

(gdb) c Continuing.

wait a bit and hit C-c

^C Program received signal SIGINT, Interrupt. 0x000000c8 in _delay_ms (ms=100) at /home/user/x/avr//avr/include/util/delay.h:187 187 builtin_avr_delay_cycles(__ticks_dc);

updi-gdbserver supports 1 hardware breakpoints, which doesn't require

rewriting flash the way software breakpoints do.

(gdb) hbreak example-led-blink.c:7 Hardware assisted breakpoint 1 at 0x4c: file example-led-blink.c, line 7.

(gdb) info br Num Type Disp Enb Address What 1 hw breakpoint keep y 0x0000004c in main at example-led-blink.c:7

(gdb) c Continuing .

after a brief moment, gdb should reply as the target hits our breakpoint

Breakpoint 1, main () at example-led-blink.c:7 7 PORTA.OUT |= (1 << PIN6_bp); _delay_ms(100);

(gdb) p/t PORTA.IN $14 = 11110001

+end_src

Note that software breakpoints are often the default for various IDEs etc. These may still work but haven't been tested extensively, and also wear down the flash. Being explicit about ~hbreak~ instead of ~break~ is therefore currently recommended.

When the target program is running, ~updi-gdbserver~ has to poll the target (currently at 100 Hz) which causes high UART activity.

** Comparison to debugWIRE

debugWIRE is an older protocol for debugging AVR devices. It's on the very popular ~attiny85~, for example. It has a lot in common with UPDI, particularly in that it's a 1-wire, half-duplex, UART-based interface that can be used to debug AVR chips with just a UART adapter.

One major advantage of UPDI versus debug-wire is that the UPDI UART baud rate is independent of target CPU speed. The target UPDI module will detect the host UPDI baudrate using the ~0x55~ mark, and reply with the same baudrate. This, for example, makes it possible to change the target CPU clockspeed during a debugging session.

** REPL

+begin_quote

⚠ This is highly experimental and subject to change.

+end_quote

There is also a Scheme REPL available where you can experiment. I find it useful to poke at peripheral registers interactively from a REPL environment before I start writing any code meant to run on the target. Below is an example of using the DAC peripheral while the target CPU is stopped.

+begin_src scheme

me@workstation> rlwrap nc localhost 1234 ;; nrepl on (./gdbserver)

> (updi-break)

> (stop!)

> (include "atdf/ATtiny414.atdf.scm")

> (set VREF.CTRLA 1) ;; 1.1V

> (set DAC0.CTRLA #b01000001) ;; OUTEN, ENABLE

> (set PORTA.OUT #b01000000) ;; PA6 output

> (define (dac n) (set DAC0.DATA n))

> (begin (dac #x80) (dac #xff) (dac #x80) (dac #x00))

+end_src

This should produce a scope trace like this:

[[./images/scope-dac.png]]

The DAC output is shown in purple, and the rather slow UPDI UART communication is shown in blue. The delays between the ~dac~ calls are caused by UART communication. This could probably be improved by investigating at guard time and UPDI clock speed.

** TODOs

Too many to mention here, the source contains a lot of them. But a few important ones: