Lego Technic, Arduino, Robots and more...

MOBROB: OTA Update for Arduino

With this post, I want to start with the description of the software for my mobile robot. One of the first things I’m setting up in the software is an OTA (Over the Air) update function for all my components on the robot. That means besides the Raspberry Pi, I want to flash the Arduino Mega as well as the Attiny from my external PC with new software. As soon as I have written a new program in the Arduino IDE for the Arduino or Attiny, the code should be uploaded directly to the corresponding controller by clicking on ‘Upload’. And that without the need to plug in a cable connection first. In this post, I show how I implement this functionality.


A look at the architecture shows that the task definition includes four components and three communication lines.


  • External Linux Workstation
  • Raspberry Pi
  • Arduino Mega 2560
  • Attiny84

communication lines:

  • Linux PC -> Raspberry Pi: WLAN (Ethernet)
  • Raspberry Pi -> Arduino Mega: USB (serial)
  • Raspberry Pi -> Attiny84: SPI
Flashing architecture

Find the whole Architecture in my previous post.

Flashing with Arduino IDE in general

Before we look at the individual components and communication lines, I would like to briefly describe the update process at Arduino in general:

  1. step: Board-Settings
    Once you have finished writing your code in the Arduino IDE, you usually select a specific board from the ‘Tools’ menu. By selecting this board, the IDE knows which parameters to pass to the compiler when compiling your code.
  2. step: Compiler
    By clicking ‘Upload’ the Arduino IDE starts the compiler with the above-mentioned parameters. The compiler converts the written C++ code (.ino file) into binary machine code. One result of this step is a .hex file. You can also see this if you have activated verbose output in the Arduino IDE under Preferences:
  1. step: Upload
    If you have enabled verbose output for upload as well, you can see that the Arduino IDE calls the avrdude tool with some parameters after compilation. One parameter is the COM port and the path of the mentioned .hex file. Afterward, the upload of the code to the USB connected board starts.

The idea of the OTA update is not to load the .hex file created on my Linux PC directly onto a board using avrdude. Instead, send the .hex file to the Raspberry Pi via WLAN. On the Raspberry Pi, the tool avrdude should then continue with the transfer to the board.

Send the .hex file to the Raspberry Pi

Sending the hex file to Raspberry Pi is divided into three blocks.

  1. To write an own transfer program on the Linux PC which sends the .hex file to the Raspberry Pi
  2. Get Arduino IDE to start my own transfer program instead of avrdude after compiling
  3. Write a program on the Raspberry Pi which receives the file and triggers a call of avrdude.

1. Own transfer program

First of all, we create our transfer program. To accomplish this task it is sufficient to write a few lines of the python script

# -*- coding: utf-8 -*-
Created on Sat Apr 27 13:12:21 2019

@author: Techniccontroller
import requests
import sys, getopt

def main(argv):
        opts, args = getopt.getopt(argv,"hu:f:",["url=","hexfile="])
    except getopt.GetoptError:
        print('GetoptError: send2Pi -u <url> -f <hexfile>')
    # extract url and path from parameters
    for opt, arg in opts:
        if opt == '-h':
            print('send2Pi -u <url> -f <hexfile>')
        elif opt in ("-u", "--url"):
            url = arg
        elif opt in ("-f", "--hexfile"):
            hexfile = arg.strip()
    print("url:" + str(url))
    print("hexfile:" + str(hexfile))
    print("uploading ...")
    # extract filename
    folders = hexfile.split("/")
    filename = folders[-1]
    #read file from filesystem
    data = open(hexfile, 'rb')
    # set header
    headers = {'filename': filename}
    # send file
    r =, data=data, headers=headers)

if __name__ == "__main__":

To get an executable application we pack the file with pyinstaller. We execute the following command in the directory where we have stored the Python script:

pyinstaller -F

The command creates a folder dist in which the executable application can be found. In my case the complete path to the application is /home/eaw/UpdateOverAir/dist/send2Pi. This command works the same way for Windows users.

2. Configuring the Arduino IDE

First, we have to make sure that we have installed the right board via the board manager. The Arduino Mega is pre-installed by default. For the Attiny84 we have to install a package manually. You can find a good tutorial here. As soon as the boards are installed a little test helps to check if the configuration for the compiler is correct. To do this, compile the code with a click on ‘Verify’ without uploading. Now let’s configure the Arduino IDE to call our transfer program send2Pi instead of avrdude after compilation.

Attiny84: First we change to the directory ~/.arduino15/packages/ATTinyCore/hardware/avr/1.3.3 and open the file platform.txt. This file defines the compilers and uploaders used by the Arduino IDE. Therefore we add the following lines to the end of the file:


# Custom upload tools
# ------------------------------


tools.overair.upload.params.quiet=-q -q
tools.overair.upload.pattern="{cmd.path}" "-u {upload.url}" "-f {build.path}/{build.project_name}.hex"

Please note that the path to application may be different for you.

The second file we need to change is also in the directory ~/.arduino15/packages/ATTinyCore/hardware/avr/1.3.3 and is called boards.txt. Here are all boards defined which are available in the package ATTinyCore. So we copy the lines for an existing board (in my case ATtiny24/44/84) and adjust the marked entries. You need to insert the hostname of the Raspberry Pi into the upload URL, in my case the hostname is mobrob.

(...) (Mobrob, OverAir)
tinymobrob.bootloader.file=empty/empty_all.hex -fno-fat-lto-objects -flto -fuse-linker-plugin
tinymobrob.bootloader.extended_fuses=0xFF MHz (internal) MHz (external) MHz (external) MHz (external) MHz (external) MHz (external) MHz (external) MHz (internal) MHz (external) MHz (external) MHz (external) MHz (external) MHz (external) MHz (internal) kHz (internal WDT)
tinymobrob.bootloader.high_fuses=0b1101{bootloader.eesave_bit}{bootloader.bod_bits} retained not retained Disabled Enabled (1.8v) Enabled (2.7v) Enabled (4.3v) (like damellis core) (like old ATTinyCore and x41-series) (saves flash) A (CW:0~7,CCW:3~10) A (CW:8~11,CCW:0~2,11){build.millis} {build.neopixelport}

Arduino Mega 2560: For the standard Arduino boards the two files are stored in the directory /opt/arduino-1.8.10/hardware/arduino/avr. In the platform.txt we insert the exact same lines as above. In the boards.txt we insert the following lines:

(...) Mega 2560 (MobRob, OverAir)
# default board may be overridden by the cpu menu
## Arduino/Genuino Mega w/ ATmega2560
## ------------------------- (Mega 2560)

After a restart of the Arduino IDE there should now be two new boards available.

3. Receive .hex-file and flash

On the Raspberry Pi, we set up a small webserver to which we send the .hex file. The .hex file is cached on the Raspberry Pi and then loaded with avrdude to the appropriate board. Again a python-script is the tool of choice to implement this functionality. The first step is to install the Arduino IDE on the Raspberry Pi so we can use avrdude.

In the following, I have mapped the complete Python code. We have to pay special attention to the two marked lines where the command is defined to load the received .hex-file to the board using avrdude. The easiest way to find out the exact structure of the command is to load the example program on the Raspberry Pi with Arduino IDE and copy the command from the verbose output. We must only change the filename.

You can find a good guide for flashing an Attiny over SPI with a Raspberry Pi here.

from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
import subprocess
from subprocess import call, PIPE
import time

# path to the folder with the temporary stored file
datapath =""

class MyHandler(BaseHTTPRequestHandler):	

	def do_POST(client):
		if client.path == "/postArduinoCode":
			# a new file for Arduino is coming
			length = client.headers['content-length']
			data =
			contentType = client.headers.get('Content-Type')
			filename = client.headers.get('filename')
			open(datapath + filename, 'wb').write(data)
			cmd = "/usr/share/arduino/hardware/tools/avrdude -C /usr/share/arduino/hardware/tools/avrdude.conf -v -p atmega2560 -c wiring -P /dev/arduino -b 115200 -D -Uflash:w:" + datapath + filename + ":i"
			proc = subprocess.Popen(cmd, stderr=PIPE, shell = True)
			res = read_stderr(proc)
			print("Output: " + res)
			client.send_header('Content-type', 'text/html')

		elif client.path == "/postAttinyISPCode":
			# a new file for Arduino is coming
			length = client.headers['content-length']
			data =
			contentType = client.headers.get('Content-Type')
			filename = client.headers.get('filename')
			open(datapath + filename, 'wb').write(data)
			call("gpio -g mode 22 out", shell = True)
			call("gpio -g write 22 0", shell = True)
			cmd = "/usr/local/bin/avrdude -c linuxspi -P /dev/spidev0.0 -p t84 -b 19200 -Uflash:w:" + datapath + filename + ":i"
			proc = subprocess.Popen(cmd , stderr=PIPE, shell = True)
			res = read_stderr(proc)
			print("Output: " + res)
			client.send_header('Content-type', 'text/html')
			call("gpio -g write 22 1", shell = True)

def read_stderr(proc):
	res = ""
	while True:
		line = proc.stderr.readline()
		if line != '':
			res += line
	return res			
def main():
		# Start webserver
		server = HTTPServer(('',8080), MyHandler)
		print('started httpserver on port 8080 ...')
		print('stop with pressing Ctrl+C')
	except KeyboardInterrupt:
		print('^C received, shutting down server')

if __name__ == '__main__':

To start the webserver we just have to execute the python-script with the following command:


If everything went well, you can now load an Arduino program from your Linux PC directly onto the Arduino Mega or Attiny. The feedback if the upload was successful is a little bit delayed than normal.

Share this post

Next Post

Previous Post

Leave a Reply

© 2020 Techniccontroller

Legal Disclosure

Privacy Policy

Data Access Request

Theme by Anders Norén