Programming and debugging a kw41z-mini with a Raspberry Pi as a JTAG adapter

March 11, 2019

I got some requests for instructions on how to flash the firmware on a kw41z-mini and this is long overdue. It flashes just like any other modern jtag/swd microcontroller that has good OpenOCD support, which is to say that the open tools we want to use are generally working well with the latest cortexm-whatever stuff we want to work with and life is pretty good right now. But if you haven't played in the 32-bit world yet then you may not have encountered OpenOCD yet, and it can take a lot of googling to figure out where to start with that. Here's a practical introduction, and of course, the oneliners you're looking for.

In this case I'm compiling RIOT-OS firmware and flashing it onto a board with a kw41z microcontroller, but this is how you could go about flashing any firmware onto any microcontroller supported by OpenOCD, which seems to be about everything in the cortexm world where all of the cool new IoT chips are.

I've been using raspberry pis in place of jtag adapters for a while and I've been incredibly happy with this setup. It's super convenient and I'm surprised more people aren't talking about how great it is. OpenOCD added support for using raspberry pi gpio pins as a jtag interface a while ago, so as long as OpenOCD supports the chip you're using then really all you need for a jtag interface is a raspberry pi. And it's like having a jtag interface on steroids that runs linux with builtin wifi and gdb and you can even compile your firmware on it if you want to. Seriously, how cool is that?


installing OpenOCD

You might need the latest version of OpenOCD if your target microcontroller is a recent chip. Today my target is a kw41z which I think isn't supported in the older version available in the raspbian repos. Here are the steps to fetch and compile v0.10.0. I chose to compile only the rpi gpio driver in case it reduced compile time. I think it compiled in about an hour on an rpi 1.2b. I don't remember how long it took on a newer pi but I expect it's much faster with more than one cpu core.

git clone --depth 1 https://git.code.sf.net/p/openocd/code openocd
cd openocd
apt-get install libtool automake
./bootstrap && ./configure --enable-bcm2835gpio --disable-jlink && time make && sudo make install

that's it; now we flash

Now you just need to know which gpio pins you've connected to your target's SWD_CLK and SWD_IO pins, as well as which configuration file to use for your type of raspberry pi and which file to use for your target microcontroller. In this case I'm using raspberrypi-native.cfg for an rpi 1.2b and k40.cfg for a kw41z. Here's the command I ended up with. This inputs an elf file and flashes and runs it.

SWDCLK_PIN=2 SWDIO_PIN=3 ; openocd -f interface/raspberrypi-native.cfg -c 'transport select swd' -c 'reset_config none separate' -c 'bcm2835gpio_swd_nums $SWDCLK_PIN $SWDIO_PIN' -f target/k40.cfg -c 'init; reset halt; program /path/to/firmware.elf verify; reset run; exit'

So yeah, a raspberry pi as a jtag interface. It's really that easy. But we can take this a lot further now since our jtag adapter can do all the linux things that a linux thing can do. Let's get on with the good stuff, like gdb debugging and supercondensed oneliners.

send locally compiled firmware to a remote pi for flashing

Presumably your pi is on a network where you can reach it. This is handy since you might prefer to do your compiling on something faster than a pi and then send the binary to the pi for flashing. Here's my handy oneliner for sending an elf over ssh and telling openocd to flash it. I couldn't manage to get openocd to accept the elf directly to stdin, so I'm just using cat to drop it in /tmp where openocd can find it. I'm also using pv to get a progress indicator for transferring the elf which is nice on slower links. If you don't want to bother with it, just replace pv with cat. Also replace jtagpi.local with the address for your pi.

SWDCLK_PIN=2 SWDIO_PIN=3 ; pv /path/to/firmware.elf | ssh root@jtagpi.local "cat > /tmp/tmp.elf && openocd -f interface/raspberrypi-native.cfg -c 'transport select swd' -c 'reset_config none separate' -c 'bcm2835gpio_swd_nums $SWDCLK_PIN $SWDIO_PIN' -f target/k40.cfg -c 'init; reset halt; program /tmp/tmp.elf verify; reset run; exit'"

Ok so that's great. But can we debug remotely with gdb too? Goodness yes we can. This is where it gets good. And it's the works-out-of-the-box-automagically kind of good.

run gdb locally, connected to OpenOCD on a remote pi

Ideally we'd like to keep our source code on our local machine and run gdb there so that we get a nice responsive interface. OpenOCD will let us run it through gdb if we set up the options right. I'm disabling the tcl and telnet interfaces so that multiple openocd instances on the same pi don't block each other as I'm not using either interface. The telnet interface is useful actually, but you can instead have gdb pass commands through by prefixing them with monitor (e.g. type monitor reset run into gdb instead of typing reset run into openocd's telnet interface).

SWDCLK_PIN=2 SWDIO_PIN=3 ; arm-none-eabi-gdb-py /path/to/firmware.elf --eval-command="target remote | ssh root@jtagpi.local \"openocd -c 'gdb_port pipe; tcl_port disabled; telnet_port disabled' -f interface/raspberrypi-native.cfg -c 'transport select swd' -c 'reset_config none separate' -c 'bcm2835gpio_swd_nums $SWDCLK_PIN $SWDIO_PIN' -f target/k40.cfg -c init\" "

That runs the arm-none-eabi version of gdb locally, telling it to ssh to a pi and run openocd there. OpenOCD is told to communicate with gdb over pipes through ssh, and since gdb is running on the same machine that compiled the firmware, it will find all the source files without any further configuration so all the backtraces and source code viewing work perfectly.

The nice thing about having gdb run openocd is first of all that it is perfectly happy to do it over ssh, which means that gdb is running on the local machine while openocd is running on the pi and we only needed one command to do it. But it also means that openocd gets started automatically when you run gdb, and it gets killed automatically when you quit gdb. I've also made sure not to add any further commands after init so that we don't disturb the state of the target in case we want to attach to it without, well, disturbing the state of the target.

the one oneliner

This is my main oneliner for compiling a RIOT firmware and sending it to an arbitrary board attached to a pi somewhere. It's especially handy when I have various boards running various RIOT firmwares connected to various pis or perhaps all on one pi. This sets up the gpio pins as well as which RIOT example to compile and which board to compile it for, then compiles it and sends it to a pi for flashing. This is run from RIOT's root directory.

SWDCLK_PIN=2 SWDIO_PIN=3 SRC=examples/gnrc_networking ; export BOARD=kw41z-mini ; make -j4 -C $SRC && pv $SRC/bin/$BOARD/*.elf | ssh root@jtagpi.local "cat > \"/tmp/$BOARD $PINS.elf\" && openocd -f interface/raspberrypi-native.cfg -c 'transport select swd' -c 'reset_config none separate' -c 'bcm2835gpio_swd_nums $SWDCLK_PIN $SWDIO_PIN' -f target/k40.cfg -c 'init; reset halt; program \"/tmp/$BOARD $PINS.elf\" verify; reset run; exit'"

what about the reset pin?

You can use the reset pin, and you may need to if your target has the jtag hardware powered down when you try to attach to it. In that case, the reset pin is the only way openocd can signal the target to power up its jtag hardware. Lately I've committed to not connecting the reset pin on anything so that I become familiar with when it's truly required versus not, so none of these commands use a reset pin. I think you just need to change -c 'reset_config none separate' to -c 'reset_config srst_only srst_open_drain' and add -c 'bcm2835gpio_srst_num $RESET_PIN' but I should come back and confirm that next time I use a reset pin.

why even bother with the 10-pin jtag headers anymore?

Good question. I'm thinking I won't put that header on anything anymore. I end up using an adapter that converts the header to .1" header pins but it's bulky and I only have two, so then I'm cutting up my good jumper wires and soldering them to the header pads and the whole thing is just silly compared to a couple of .1" pins in place of the 10-pin jtag header and they take up less space anyway.

ok but I actually did want to flash RIOT onto a kw41z-mini

Oh yeah here's the rest of that. I think we covered everything except actually producing a RIOT firmware binary. Assuming you're on some kind of modern linux (in fact you can do this 100% on a pi if you're in need of development box from scratch) all you need is to install the arm-none-eabi version of gcc/gdb and fetch RIOT from github. Here's how to do that on a pi.

apt-get install gcc-arm-none-eabi gdb-arm-none-eabi git
git clone https://github.com/RIOT-OS/RIOT.git

Most of what you git is RIOT and its drivers and included libraries and such. The stuff in examples/ is application code. Each directory in here is a thing that you can compile and flash onto a board. You can copy and modify one of those to start your application. A good starting point in general is the gnrc_networking example, or if you're on the branch with kw41z-mini support you can snoop on my telnet_shell example, which is more of a sandbox I've been playing in, but there you can see some less pristine examples of what you might expect it to look like as you start hacking an example to do what you need. That branch also has some awful hacks to the kernel that are required for telnet to work.

From RIOT's root directory you can BOARD=kw41z-mini make -j4 -C examples/gnrc_networking to build a given example for a given board. You'll find the compiled firmware in a bin/ directory inside the given example directory. Also you can export your board and cd into an example directory and then the command to compile is shorter.

export BOARD=kw41z-mini
cd examples/gnrc_networking
make -j4

In either case the firmware ends up at examples/gnrc_networking/bin/kw41z-mini/gnrc_networking.elf .

references

https://github.com/synthetos/PiOCD/wiki/Using-a-Raspberry-Pi-as-a-JTAG-Dongle
https://learn.adafruit.com/programming-microcontrollers-using-openocd-on-raspberry-pi?view=all
https://petervanhoyweghen.wordpress.com/2015/10/11/burning-zero-bootloader-with-beaglebone-as-swd-programmer/

9 Comments

Beichen Yang

Oct. 15, 2019, 8:04 p.m.

Hello,

I just bought two Pi 6LoWPAN SLIP radios and connected with the raspberry pi 3 B. I came into some problems when trying to set up the openocd for flashing. I don't know what is the correct GPIO pin number for SWD_CLK and SWD_IO. And it seems that the interface/raspberrypi-native.cfg is for raspberry pi one rather than pi 3, since pi 3 is using BCM2837 instead of BCM2835 indicated in this file. Then how could I modify the file and the command line for using OpenOCD to flush?

Best,
Beichen

Beichen Yang

Nov. 20, 2019, 12:14 p.m.

I saw the update on Pi 6LoWPAN SLIP radios, and I had already converted two of the pi into the border router. The problem was that those two routers can not discover each other. I also checked the other tutorial like
https://morschi.com/2017/04/05/setup-a-riot-os-6lowpan-border-router/
which said that I need to flash one radio with gnrc_border_router and the other radio with gnrc_networking to make them communicate. So I ran the command

sudo ETHOS_BAUDRATE=115200 DEBUG_ADAPTER=bcm2835gpio BOARD=openlabs-kw41z-slip make -j4 flash -C examples/gnrc_networking/

to flash one radio with gnrc_networking. Then I ran

picocom --imap lfcrlf /dev/serial0 -b 115200

to get into the Riot system on the radio. I can get into the riot with no problem, but the border router still can not find the neighbor. What should I do?

benemorius

Nov. 29, 2019, 11:32 p.m.

Sorry I missed this before.

They should normally be able to see each other as long as they're on the same channel and PAN ID. You can double check those with `ifconfig` but of course they'd be the same if you didn't change them.

But "see each other" can be ambiguous. Generally the best way I've found to test for basic connectivity is to run `ping6 ff02::1` and any node in range should reply. Does that work from either node? If that doesn't work, nothing else will either.

You could try a different channel in case there's too much interference on the default one. Also you could use a sniffer to check what packets are actually coming through. But normally they should just be able to ping each other out of the box.

Beichen Yang

Dec. 1, 2019, 3:51 p.m.

Even I changed the channel to 18 and keep the pan ID as 0x23, the boarder router still can not discovered the neighbor.
The message is like:
uhcp_client(): sending REQ...
got packet from fe80::1 port 54982
uhcp: push from fe80::1:54982 prefix=2001:db8:0:1540::/64
gnrc_uhcpc: uhcp_handle_prefix(): got same prefix again
uhcp_client(): sending REQ...
got packet from fe80::1 port 55992
uhcp: push from fe80::1:55992 prefix=2001:db8:0:1540::/64
gnrc_uhcpc: uhcp_handle_prefix(): got same prefix again
uhcp_client(): sending REQ...
got packet from fe80::1 port 52004
uhcp: push from fe80::1:52004 prefix=2001:db8:0:1540::/64
gnrc_uhcpc: uhcp_handle_prefix(): got same prefix again
uhcp_client(): sending REQ...
got packet from fe80::1 port 58550
uhcp: push from fe80::1:58550 prefix=2001:db8:0:1540::/64
gnrc_uhcpc: uhcp_handle_prefix(): got same prefix again
uhcp_client(): sending REQ...
got packet from fe80::1 port 41244
uhcp: push from fe80::1:41244 prefix=2001:db8:0:1540::/64
gnrc_uhcpc: uhcp_handle_prefix(): got same prefix again
uhcp_client(): sending REQ...
got packet from fe80::1 port 60786
uhcp: push from fe80::1:60786 prefix=2001:db8:0:1540::/64
gnrc_uhcpc: uhcp_handle_prefix(): got same prefix again

Beichen Yang

Dec. 1, 2019, 3:56 p.m.

Also, ifconfig command is like

ifconfig
Iface 6 HWaddr: 02:8D:5A:FC:7C:6B
L2-PDU:1500 MTU:1500 HL:64 RTR
RTR_ADV Source address length: 6
Link type: wired
inet6 addr: fe80::8d:5aff:fefc:7c6b scope: local VAL
inet6 addr: fe80::2 scope: local VAL
inet6 group: ff02::2
inet6 group: ff02::1
inet6 group: ff02::1:fffc:7c6b
inet6 group: ff02::1:ff00:2

Iface 7 HWaddr: 6D:5A Channel: 18 Page: 0 NID: 0x23
Long HWaddr: 23:1F:73:2F:61:A2:6D:5A
TX-Power: 0dBm State: IDLE max. Retrans.: 3 CSMA Retries: 4
ACK_REQ CSMA L2-PDU:102 MTU:1280 HL:64 RTR
RTR_ADV 6LO IPHC
Source address length: 8
Link type: wireless
inet6 addr: fe80::211f:732f:61a2:6d5a scope: local VAL
inet6 addr: 2001:db8:0:1540:211f:732f:61a2:6d5a scope: global VAL
inet6 group: ff02::2
inet6 group: ff02::1
inet6 group: ff02::1:ffa2:6d5a


nib neigh command is like:

> nib neigh
fe80::1 dev #6 lladdr 12:BE:82:8A:BA:C8 STALE GC

Beichen Yang

Dec. 1, 2019, 3:59 p.m.

As for the gnrc_networking board:
> nib neigh
> ifconfig
Iface 7 HWaddr: 6D:72 Channel: 18 Page: 0 NID: 0x23
Long HWaddr: 23:66:73:2D:61:A2:6D:72
TX-Power: 0dBm State: IDLE max. Retrans.: 3 CSMA Retries: 4
ACK_REQ CSMA L2-PDU:102 MTU:1280 HL:64 RTR
6LO IPHC
Source address length: 8
Link type: wireless
inet6 addr: fe80::2166:732d:61a2:6d72 scope: local VAL
inet6 group: ff02::2
inet6 group: ff02::1
inet6 group: ff02::1:ffa2:6d72
inet6 group: ff02::1a

Statistics for Layer 2
RX packets 0 bytes 0
TX packets 9730 (Multicast: 9730) bytes 418340
TX succeeded 9730 errors 0
Statistics for IPv6
RX packets 0 bytes 0
TX packets 9730 (Multicast: 9730) bytes 622670
TX succeeded 9730 errors 0

> ping6 ff02::1

--- ff02::1 PING statistics ---
3 packets transmitted, 0 packets received, 100% packet loss

Beichen Yang

Dec. 1, 2019, 4:07 p.m.

Running ping6 ff02::1 on the boarder router board will automaticlly reset the channel into 26. Last week, I also took both of the radio board to a wireless communication lab and used their signal spectrum analyzing tool to detect whether these two board were generating wireless signal or not. And I couldn't detect any significant signal between 2400 MHZ and 2500 MHZ. The channel 26 should working on 2480 MHZ, but I coudn't detect anything beside background noise. Even if I channged the channel number to 20 or 22, still nothing was there. So what could be the problem?

Leone

May 5, 2019, 2:06 a.m.

can I use this to program frdm-kw41z?

benemorius

May 5, 2019, 9:31 p.m.

You could but frdm-kw41z already includes a programmer. But yes if you want to I'm sure you can connect to the swd header and program it directly.

Leave a comment