Possible to upload via RaspberryPi UART?

I’m trying to figure out how to program a 328p from a RPi 3b/4 using the hardware UART (/dev/ttyS0) on the RPi. I think it should be possible as long as I can trigger an external reset on the 328p. Is it possible to configure an arbitrary GPIO on the RPi as a reset pin for the upload?

The board is a custom PCB with ATMega328p at 8MHz. It sends sensor data to an RPi using serial over the UART(s). I’ve been uploading from my workstation using PlatformIO within Visual Studio using an ICSP header. However, once the project is “done” and in-situ, it will be much more convenient to upload new firmware using the RPi.

So usually the “DTR” pin of a USB-UART adapter is connected via a 100nF capacitor to the RESET pin of the 328P (schematics). A pulse on DTR translates into a reset and the Uno board will very shorty be in bootloader mode, waiting t oaccept a new firmware.

  1. This post says while it has hardware RTS + CTR pins, there is no hardware DTR pin. So you would have to implement that as a “software-defined” DTR pin. You could do so by registering a pre-upload hook using advanced scripting, during which you can use the RPi library to pulse the pin. Then, the upload method can be kept as default (upload_protocol = arduino = aka serial.)
  2. avrdude natively supports the raspberry_pi_gpio upload protocol, through which you can connect the shown GPIO pins to the ISP header of the ATMega328P, aka, its SPI pins (+reset). See documentation on the general gist of how to configure upload using differing protocol.
1 Like

Thanks @maxgerhardt! following your first suggestion, I was able to get platformio to program an Arduino Nano from a RPi over UART.

Here is my platformio.ini

[env:nanoatmega328]
platform = atmelavr
board = nanoatmega328
framework = arduino
extra_scripts = reset_nano.py
upload_port = /dev/ttyS0

And the script itself:

import time
import RPi.GPIO as GPIO

Import("env")
GPIO.setmode(GPIO.BOARD)
GPIO.setup(7, GPIO.OUT)

def before_upload(source, target, env):
    print("Reset Nano")
    GPIO.output(7, GPIO.LOW)
    time.sleep(0.010)
    GPIO.output(7, GPIO.HIGH)

env.AddPreAction("upload", before_upload)

I tested this by powering the Nano from the 5V on the RPi as well as external power. Both worked just fine.

Now I’m running into some trouble when trying to replicate this on my custom board. It seems that the main program is starting up too quickly and avrdude isn’t able to hit the bootloader. From what I can tell, optiboot can be configured to wait up to 8 seconds for a programmer to communicate with it, but I can’t see how to set that configuration via platformio.

Do I need to build a custom optiboot with a higher timeout?

Are you sure Optiboot was burned into your custom ATMega328P board beforehand? If it has no bootloader, UART upload is impossible. Also, the wait time in the bootloader is predetermined by the burned bootloader, that is not configurable from the outside (and thus in PlatformIO), you would need to reburn the bootloader.

Are you directly hooking into the RST pin of the custom board or is there a capacitor in between the Pi’s “7” pin and your board’s RST pin? (Like in the reference boads.)

I had a basic misunderstanding of upload mechanisms and boot loaders. I was burning a custom bootloader via ISP and and then uploading my main program also over ISP. I didn’t realize this wiped out the bootloader.

After realizing this, I am able to upload over serial with no issues. Here is what I did (for future readers)

Custom optiboot build (from optiboot repo)

make clean AVR_FREQ=8000000 BAUD_RATE=38400 LED=D7 LED_START_FLASHES=3 UART=0 TIMEOUT=2 atmega328

(D7 is pin 13 on the ATMega328p)

Tell platformio to upload this bootloader

platform = atmelavr
board = ATmega328P
framework = arduino
upload_protocol = stk500v2
upload_flags = 
	-v
	-e
	-P/dev/cu.usbmodem003564172
upload_port = /dev/cu.usbmodem003564172
upload_speed = 38400
board_bootloader.file=my_custom_optiboot_atmega328.hex
board_bootloader.lfuse = 0xF7
board_bootloader.hfuse = 0xD6
board_bootloader.efuse = 0xFD
board_bootloader.lock_bits = 0x0F
board_bootloader.unlock_bits = 0x3F

This was done from my Mac over an Adafruit ISP programmer.

Then, over on the RPi, I have the following platformio config:

[env:ATmega328P]
platform = atmelavr
board = ATmega328P
framework = arduino
extra_scripts = reset_nano.py
upload_port = /dev/ttyS0
upload_speed = 38400
board_bootloader.lfuse = 0xF7
board_bootloader.hfuse = 0xD6
board_bootloader.efuse = 0xFD
board_bootloader.lock_bits = 0x0F
board_bootloader.unlock_bits = 0x3F
board_build.f_cpu = 8000000L

With the same reset_nano.py script as above.

Are you directly hooking into the RST pin of the custom board or is there a capacitor in between the Pi’s “7” pin and your board’s RST pin? (Like in the reference boads.)

Just directly connected. Is a capacitor recommended? I’ll need to do a new PCB revision anyways to get the RST pin connected to my RPi header.

Thanks again for the help!

The capacitor is an AC coupler in this case. I think this post is right in that regard. If RESET doesn’t go anywhere else in the system and the Pi can control it freely through the script, I don’t see why the cap would be required though.

You are expected to install the necessary Python package inside PlatformIO’s python environment. If you installed PlatformIO globally via pip3, you need to do a pip3 install RPi.GPIO. If you installed PlatformIO regularly or with the installer script, find the relevant Python executable with pio system info → “Python executabl” and execute that binary with -m pip install RPi.GPIO at the end.