2012-09-16 16:26:14 -03:00
#!/usr/bin/env python
2012-08-04 19:12:36 -03:00
############################################################################
#
# Copyright (C) 2012 PX4 Development Team. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
# 3. Neither the name PX4 nor the names of its contributors may be
# used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
############################################################################
#
# Serial firmware uploader for the PX4FMU bootloader
#
# The PX4 firmware file is a JSON-encoded Python object, containing
# metadata fields and a zlib-compressed base64-encoded firmware image.
#
# The uploader uses the following fields from the firmware file:
#
# image
# The firmware that will be uploaded.
# image_size
# The size of the firmware in bytes.
# board_id
# The board for which the firmware is intended.
# board_revision
# Currently only used for informational purposes.
#
import sys
import argparse
import binascii
import serial
import os
import struct
import json
import zlib
import base64
import time
2012-10-02 05:41:21 -03:00
import array
2012-08-04 19:12:36 -03:00
from sys import platform as _platform
class firmware ( object ) :
''' Loads a firmware file '''
desc = { }
2012-10-02 05:41:21 -03:00
image = bytes ( )
crctab = array . array ( ' I ' , [
0x00000000 , 0x77073096 , 0xee0e612c , 0x990951ba , 0x076dc419 , 0x706af48f , 0xe963a535 , 0x9e6495a3 ,
0x0edb8832 , 0x79dcb8a4 , 0xe0d5e91e , 0x97d2d988 , 0x09b64c2b , 0x7eb17cbd , 0xe7b82d07 , 0x90bf1d91 ,
0x1db71064 , 0x6ab020f2 , 0xf3b97148 , 0x84be41de , 0x1adad47d , 0x6ddde4eb , 0xf4d4b551 , 0x83d385c7 ,
0x136c9856 , 0x646ba8c0 , 0xfd62f97a , 0x8a65c9ec , 0x14015c4f , 0x63066cd9 , 0xfa0f3d63 , 0x8d080df5 ,
0x3b6e20c8 , 0x4c69105e , 0xd56041e4 , 0xa2677172 , 0x3c03e4d1 , 0x4b04d447 , 0xd20d85fd , 0xa50ab56b ,
0x35b5a8fa , 0x42b2986c , 0xdbbbc9d6 , 0xacbcf940 , 0x32d86ce3 , 0x45df5c75 , 0xdcd60dcf , 0xabd13d59 ,
0x26d930ac , 0x51de003a , 0xc8d75180 , 0xbfd06116 , 0x21b4f4b5 , 0x56b3c423 , 0xcfba9599 , 0xb8bda50f ,
0x2802b89e , 0x5f058808 , 0xc60cd9b2 , 0xb10be924 , 0x2f6f7c87 , 0x58684c11 , 0xc1611dab , 0xb6662d3d ,
0x76dc4190 , 0x01db7106 , 0x98d220bc , 0xefd5102a , 0x71b18589 , 0x06b6b51f , 0x9fbfe4a5 , 0xe8b8d433 ,
0x7807c9a2 , 0x0f00f934 , 0x9609a88e , 0xe10e9818 , 0x7f6a0dbb , 0x086d3d2d , 0x91646c97 , 0xe6635c01 ,
0x6b6b51f4 , 0x1c6c6162 , 0x856530d8 , 0xf262004e , 0x6c0695ed , 0x1b01a57b , 0x8208f4c1 , 0xf50fc457 ,
0x65b0d9c6 , 0x12b7e950 , 0x8bbeb8ea , 0xfcb9887c , 0x62dd1ddf , 0x15da2d49 , 0x8cd37cf3 , 0xfbd44c65 ,
0x4db26158 , 0x3ab551ce , 0xa3bc0074 , 0xd4bb30e2 , 0x4adfa541 , 0x3dd895d7 , 0xa4d1c46d , 0xd3d6f4fb ,
0x4369e96a , 0x346ed9fc , 0xad678846 , 0xda60b8d0 , 0x44042d73 , 0x33031de5 , 0xaa0a4c5f , 0xdd0d7cc9 ,
0x5005713c , 0x270241aa , 0xbe0b1010 , 0xc90c2086 , 0x5768b525 , 0x206f85b3 , 0xb966d409 , 0xce61e49f ,
0x5edef90e , 0x29d9c998 , 0xb0d09822 , 0xc7d7a8b4 , 0x59b33d17 , 0x2eb40d81 , 0xb7bd5c3b , 0xc0ba6cad ,
0xedb88320 , 0x9abfb3b6 , 0x03b6e20c , 0x74b1d29a , 0xead54739 , 0x9dd277af , 0x04db2615 , 0x73dc1683 ,
0xe3630b12 , 0x94643b84 , 0x0d6d6a3e , 0x7a6a5aa8 , 0xe40ecf0b , 0x9309ff9d , 0x0a00ae27 , 0x7d079eb1 ,
0xf00f9344 , 0x8708a3d2 , 0x1e01f268 , 0x6906c2fe , 0xf762575d , 0x806567cb , 0x196c3671 , 0x6e6b06e7 ,
0xfed41b76 , 0x89d32be0 , 0x10da7a5a , 0x67dd4acc , 0xf9b9df6f , 0x8ebeeff9 , 0x17b7be43 , 0x60b08ed5 ,
0xd6d6a3e8 , 0xa1d1937e , 0x38d8c2c4 , 0x4fdff252 , 0xd1bb67f1 , 0xa6bc5767 , 0x3fb506dd , 0x48b2364b ,
0xd80d2bda , 0xaf0a1b4c , 0x36034af6 , 0x41047a60 , 0xdf60efc3 , 0xa867df55 , 0x316e8eef , 0x4669be79 ,
0xcb61b38c , 0xbc66831a , 0x256fd2a0 , 0x5268e236 , 0xcc0c7795 , 0xbb0b4703 , 0x220216b9 , 0x5505262f ,
0xc5ba3bbe , 0xb2bd0b28 , 0x2bb45a92 , 0x5cb36a04 , 0xc2d7ffa7 , 0xb5d0cf31 , 0x2cd99e8b , 0x5bdeae1d ,
0x9b64c2b0 , 0xec63f226 , 0x756aa39c , 0x026d930a , 0x9c0906a9 , 0xeb0e363f , 0x72076785 , 0x05005713 ,
0x95bf4a82 , 0xe2b87a14 , 0x7bb12bae , 0x0cb61b38 , 0x92d28e9b , 0xe5d5be0d , 0x7cdcefb7 , 0x0bdbdf21 ,
0x86d3d2d4 , 0xf1d4e242 , 0x68ddb3f8 , 0x1fda836e , 0x81be16cd , 0xf6b9265b , 0x6fb077e1 , 0x18b74777 ,
0x88085ae6 , 0xff0f6a70 , 0x66063bca , 0x11010b5c , 0x8f659eff , 0xf862ae69 , 0x616bffd3 , 0x166ccf45 ,
0xa00ae278 , 0xd70dd2ee , 0x4e048354 , 0x3903b3c2 , 0xa7672661 , 0xd06016f7 , 0x4969474d , 0x3e6e77db ,
0xaed16a4a , 0xd9d65adc , 0x40df0b66 , 0x37d83bf0 , 0xa9bcae53 , 0xdebb9ec5 , 0x47b2cf7f , 0x30b5ffe9 ,
0xbdbdf21c , 0xcabac28a , 0x53b39330 , 0x24b4a3a6 , 0xbad03605 , 0xcdd70693 , 0x54de5729 , 0x23d967bf ,
0xb3667a2e , 0xc4614ab8 , 0x5d681b02 , 0x2a6f2b94 , 0xb40bbe37 , 0xc30c8ea1 , 0x5a05df1b , 0x2d02ef8d ] )
crcpad = bytearray ( ' \xff \xff \xff \xff ' )
2012-08-04 19:12:36 -03:00
def __init__ ( self , path ) :
# read the file
f = open ( path , " r " )
self . desc = json . load ( f )
f . close ( )
2012-10-02 05:41:21 -03:00
self . image = bytearray ( zlib . decompress ( base64 . b64decode ( self . desc [ ' image ' ] ) ) )
2012-08-04 19:12:36 -03:00
2012-08-11 23:42:24 -03:00
# pad image to 4-byte length
while ( ( len ( self . image ) % 4 ) != 0 ) :
2012-10-02 05:41:21 -03:00
self . image . append ( ' \xff ' )
2012-08-11 23:42:24 -03:00
2012-08-04 19:12:36 -03:00
def property ( self , propname ) :
return self . desc [ propname ]
2012-10-02 05:41:21 -03:00
def __crc32 ( self , bytes , state ) :
for byte in bytes :
index = ( state ^ byte ) & 0xff
state = self . crctab [ index ] ^ ( state >> 8 )
return state
def crc ( self , padlen ) :
state = self . __crc32 ( self . image , int ( 0 ) )
for i in range ( len ( self . image ) , ( padlen - 1 ) , 4 ) :
state = self . __crc32 ( self . crcpad , state )
return state
2012-08-04 19:12:36 -03:00
class uploader ( object ) :
''' Uploads a firmware file to the PX FMU bootloader '''
2012-10-02 05:41:21 -03:00
# protocol bytes
2012-08-04 19:12:36 -03:00
INSYNC = chr ( 0x12 )
EOC = chr ( 0x20 )
2012-10-02 05:41:21 -03:00
# reply bytes
OK = chr ( 0x10 )
FAILED = chr ( 0x11 )
INVALID = chr ( 0x13 ) # rev3+
# command bytes
NOP = chr ( 0x00 ) # guaranteed to be discarded by the bootloader
2012-08-04 19:12:36 -03:00
GET_SYNC = chr ( 0x21 )
GET_DEVICE = chr ( 0x22 )
CHIP_ERASE = chr ( 0x23 )
2012-10-02 05:41:21 -03:00
CHIP_VERIFY = chr ( 0x24 ) # rev2 only
2012-08-04 19:12:36 -03:00
PROG_MULTI = chr ( 0x27 )
2012-10-02 05:41:21 -03:00
READ_MULTI = chr ( 0x28 ) # rev2 only
GET_CRC = chr ( 0x29 ) # rev3+
2012-08-04 19:12:36 -03:00
REBOOT = chr ( 0x30 )
INFO_BL_REV = chr ( 1 ) # bootloader protocol revision
2012-09-20 01:52:42 -03:00
BL_REV_MIN = 2 # minimum supported bootloader protocol
BL_REV_MAX = 3 # maximum supported bootloader protocol
2012-08-04 19:12:36 -03:00
INFO_BOARD_ID = chr ( 2 ) # board type
INFO_BOARD_REV = chr ( 3 ) # board revision
INFO_FLASH_SIZE = chr ( 4 ) # max firmware size in bytes
PROG_MULTI_MAX = 60 # protocol max is 255, must be multiple of 4
READ_MULTI_MAX = 60 # protocol max is 255, something overflows with >= 64
def __init__ ( self , portname , baudrate ) :
2012-08-22 21:06:58 -03:00
# open the port, keep the default timeout short so we can poll quickly
self . port = serial . Serial ( portname , baudrate , timeout = 0.25 )
2012-08-04 19:12:36 -03:00
def close ( self ) :
if self . port is not None :
self . port . close ( )
def __send ( self , c ) :
# print("send " + binascii.hexlify(c))
self . port . write ( str ( c ) )
def __recv ( self , count = 1 ) :
c = self . port . read ( count )
2012-10-02 05:41:21 -03:00
if len ( c ) < 1 :
2012-08-04 19:12:36 -03:00
raise RuntimeError ( " timeout waiting for data " )
# print("recv " + binascii.hexlify(c))
return c
2012-10-02 05:41:21 -03:00
def __recv_int ( self ) :
raw = self . __recv ( 4 )
val = struct . unpack ( " <I " , raw )
return val [ 0 ]
2012-08-04 19:12:36 -03:00
def __getSync ( self ) :
self . port . flush ( )
c = self . __recv ( )
2012-10-02 05:41:21 -03:00
if c is not self . INSYNC :
2012-08-04 19:12:36 -03:00
raise RuntimeError ( " unexpected 0x %x instead of INSYNC " % ord ( c ) )
c = self . __recv ( )
2012-10-02 05:41:21 -03:00
if c == self . INVALID :
raise RuntimeError ( " bootloader reports INVALID OPERATION " )
if c == self . FAILED :
raise RuntimeError ( " bootloader reports OPERATION FAILED " )
if c != self . OK :
raise RuntimeError ( " unexpected response 0x %x instead of OK " % ord ( c ) )
2012-08-04 19:12:36 -03:00
# attempt to get back into sync with the bootloader
def __sync ( self ) :
# send a stream of ignored bytes longer than the longest possible conversation
# that we might still have in progress
# self.__send(uploader.NOP * (uploader.PROG_MULTI_MAX + 2))
self . port . flushInput ( )
self . __send ( uploader . GET_SYNC
+ uploader . EOC )
self . __getSync ( )
2012-09-16 16:26:14 -03:00
# def __trySync(self):
# c = self.__recv()
# if (c != self.INSYNC):
# #print("unexpected 0x%x instead of INSYNC" % ord(c))
# return False;
# c = self.__recv()
# if (c != self.OK):
# #print("unexpected 0x%x instead of OK" % ord(c))
# return False
# return True
2012-08-04 19:12:36 -03:00
# send the GET_DEVICE command and wait for an info parameter
def __getInfo ( self , param ) :
self . __send ( uploader . GET_DEVICE + param + uploader . EOC )
2012-10-02 05:41:21 -03:00
value = self . __recv_int ( )
2012-08-04 19:12:36 -03:00
self . __getSync ( )
2012-10-02 05:41:21 -03:00
return value
2012-08-04 19:12:36 -03:00
# send the CHIP_ERASE command and wait for the bootloader to become ready
def __erase ( self ) :
self . __send ( uploader . CHIP_ERASE
+ uploader . EOC )
2012-08-22 21:06:58 -03:00
# erase is very slow, give it 10s
2012-09-10 02:09:17 -03:00
deadline = time . time ( ) + 10
while time . time ( ) < deadline :
try :
self . __getSync ( )
return
except RuntimeError as ex :
# we timed out, that's OK
continue
raise RuntimeError ( " timed out waiting for erase " )
2012-08-04 19:12:36 -03:00
# send a PROG_MULTI command to write a collection of bytes
def __program_multi ( self , data ) :
self . __send ( uploader . PROG_MULTI
+ chr ( len ( data ) ) )
self . __send ( data )
self . __send ( uploader . EOC )
self . __getSync ( )
# verify multiple bytes in flash
def __verify_multi ( self , data ) :
self . __send ( uploader . READ_MULTI
+ chr ( len ( data ) )
+ uploader . EOC )
2012-09-16 16:26:14 -03:00
self . port . flush ( )
2012-08-04 19:12:36 -03:00
programmed = self . __recv ( len ( data ) )
2012-10-02 05:41:21 -03:00
if programmed != data :
2012-08-04 19:12:36 -03:00
print ( " got " + binascii . hexlify ( programmed ) )
print ( " expect " + binascii . hexlify ( data ) )
return False
self . __getSync ( )
return True
# send the reboot command
def __reboot ( self ) :
2012-10-02 05:41:21 -03:00
self . __send ( uploader . REBOOT
+ uploader . EOC )
2012-08-04 19:12:36 -03:00
self . port . flush ( )
2012-10-02 05:41:21 -03:00
# v3+ can report failure if the first word flash fails
if self . bl_rev > = 3 :
self . __getSync ( )
2012-08-04 19:12:36 -03:00
# split a sequence into a list of size-constrained pieces
def __split_len ( self , seq , length ) :
return [ seq [ i : i + length ] for i in range ( 0 , len ( seq ) , length ) ]
# upload code
def __program ( self , fw ) :
code = fw . image
groups = self . __split_len ( code , uploader . PROG_MULTI_MAX )
for bytes in groups :
self . __program_multi ( bytes )
# verify code
2012-10-02 05:41:21 -03:00
def __verify_v2 ( self , fw ) :
2012-08-04 19:12:36 -03:00
self . __send ( uploader . CHIP_VERIFY
+ uploader . EOC )
self . __getSync ( )
code = fw . image
groups = self . __split_len ( code , uploader . READ_MULTI_MAX )
for bytes in groups :
if ( not self . __verify_multi ( bytes ) ) :
raise RuntimeError ( " Verification failed " )
2012-10-02 05:41:21 -03:00
def __verify_v3 ( self , fw ) :
expect_crc = fw . crc ( self . fw_maxsize )
self . __send ( uploader . GET_CRC
+ uploader . EOC )
report_crc = self . __recv_int ( )
self . __getSync ( )
if report_crc != expect_crc :
print ( " Expected 0x %x " % expect_crc )
print ( " Got 0x %x " % report_crc )
raise RuntimeError ( " Program CRC failed " )
2012-08-04 19:12:36 -03:00
# get basic data about the board
def identify ( self ) :
# make sure we are in sync before starting
self . __sync ( )
# get the bootloader protocol ID first
2012-10-02 05:41:21 -03:00
self . bl_rev = self . __getInfo ( uploader . INFO_BL_REV )
if ( self . bl_rev < uploader . BL_REV_MIN ) or ( self . bl_rev > uploader . BL_REV_MAX ) :
2012-09-20 01:52:42 -03:00
print ( " Unsupported bootloader protocol %d " % uploader . INFO_BL_REV )
2012-08-04 19:12:36 -03:00
raise RuntimeError ( " Bootloader protocol mismatch " )
self . board_type = self . __getInfo ( uploader . INFO_BOARD_ID )
self . board_rev = self . __getInfo ( uploader . INFO_BOARD_REV )
self . fw_maxsize = self . __getInfo ( uploader . INFO_FLASH_SIZE )
# upload the firmware
def upload ( self , fw ) :
# Make sure we are doing the right thing
if self . board_type != fw . property ( ' board_id ' ) :
2012-11-30 21:03:30 -04:00
raise RuntimeError ( " Firmware not suitable for this board (run ' make configure_px4fmu && make clean ' or ' make configure_px4io && make clean ' to reconfigure). " )
2012-08-04 19:12:36 -03:00
if self . fw_maxsize < fw . property ( ' image_size ' ) :
raise RuntimeError ( " Firmware image is too large for this board " )
print ( " erase... " )
self . __erase ( )
print ( " program... " )
self . __program ( fw )
2012-10-02 04:23:47 -03:00
print ( " verify... " )
2012-10-02 05:41:21 -03:00
if self . bl_rev == 2 :
self . __verify_v2 ( fw )
else :
self . __verify_v3 ( fw )
2012-08-04 19:12:36 -03:00
print ( " done, rebooting. " )
self . __reboot ( )
self . port . close ( )
# Parse commandline arguments
parser = argparse . ArgumentParser ( description = " Firmware uploader for the PX autopilot system. " )
parser . add_argument ( ' --port ' , action = " store " , required = True , help = " Serial port(s) to which the FMU may be attached " )
parser . add_argument ( ' --baud ' , action = " store " , type = int , default = 115200 , help = " Baud rate of the serial port (default is 115200), only required for true serial ports. " )
parser . add_argument ( ' firmware ' , action = " store " , help = " Firmware file to be uploaded " )
args = parser . parse_args ( )
# Load the firmware file
fw = firmware ( args . firmware )
print ( " Loaded firmware for %x , %x , waiting for the bootloader... " % ( fw . property ( ' board_id ' ) , fw . property ( ' board_revision ' ) ) )
# Spin waiting for a device to show up
while True :
for port in args . port . split ( " , " ) :
#print("Trying %s" % port)
# create an uploader attached to the port
try :
2012-10-02 04:23:47 -03:00
if " linux " in _platform :
# Linux, don't open Mac OS and Win ports
if not " COM " in port and not " tty.usb " in port :
up = uploader ( port , args . baud )
elif " darwin " in _platform :
# OS X, don't open Windows and Linux ports
if not " COM " in port and not " ACM " in port :
up = uploader ( port , args . baud )
elif " win " in _platform :
# Windows, don't open POSIX ports
if not " / " in port :
up = uploader ( port , args . baud )
2012-08-04 19:12:36 -03:00
except :
# open failed, rate-limit our attempts
time . sleep ( 0.05 )
# and loop to the next port
continue
# port is open, try talking to it
try :
# identify the bootloader
up . identify ( )
2012-10-02 05:41:21 -03:00
print ( " Found board %x , %x bootloader rev %x on %s " % ( up . board_type , up . board_rev , up . bl_rev , port ) )
2012-08-04 19:12:36 -03:00
except :
# most probably a timeout talking to the port, no bootloader
continue
try :
# ok, we have a bootloader, try flashing it
up . upload ( fw )
except RuntimeError as ex :
# print the error
print ( " ERROR: %s " % ex . args )
finally :
# always close the port
up . close ( )
# we could loop here if we wanted to wait for more boards...
sys . exit ( 0 )