|
@@ -2,16 +2,15 @@
|
|
|
|
|
|
use strict;
|
|
|
use integer;
|
|
|
+use IO::Handle;
|
|
|
use PerlIO::gzip;
|
|
|
use File::Temp;
|
|
|
use File::Spec;
|
|
|
+use File::HomeDir;
|
|
|
use Time::HiRes qw(usleep tv_interval);
|
|
|
use Digest::CRC qw(crc32);
|
|
|
use v5.10; # For "state"
|
|
|
|
|
|
-my $esptool = 'esptool';
|
|
|
-$esptool = $ENV{'ESPTOOL'} || $esptool;
|
|
|
-
|
|
|
my $esp_retries = 10;
|
|
|
|
|
|
my @FW_MAGIC = (undef, 0x7a07fbd6, 0xa924ed0b);
|
|
@@ -120,8 +119,10 @@ sub unquote_cmd($) {
|
|
|
my @a;
|
|
|
|
|
|
$s =~ s/[\r\n]+/ /g;
|
|
|
- while ($s =~ /^\s*(?:\"((?:[^\"]+|\"\")*)\"|(\S+))(\s.*)?$/) {
|
|
|
- push(@a, $1.$2);
|
|
|
+ while ($s =~ /^\s*(?:\"((?:[^\"]+|\"\")*)\"|([^\"]\S*))(\s.*)?$/) {
|
|
|
+ my $a = $1;
|
|
|
+ $a =~ s/\"\"/\"/g;
|
|
|
+ push(@a, $a.$2);
|
|
|
$s = $3;
|
|
|
}
|
|
|
|
|
@@ -145,12 +146,120 @@ sub hash2opt($)
|
|
|
return map { $h->{$_} ne '' ? ('--'.$_, $h->{$_}) : () } sort keys %{$h};
|
|
|
}
|
|
|
|
|
|
+my $esptool;
|
|
|
+
|
|
|
+# Try running esptool --help and look for a version string
|
|
|
+sub try_esptool($) {
|
|
|
+ use Data::Dump 'pp';
|
|
|
+
|
|
|
+ my($cmd) = @_;
|
|
|
+
|
|
|
+ return undef if ($cmd eq '');
|
|
|
+
|
|
|
+ my @espcmd = unquote_cmd($cmd);
|
|
|
+
|
|
|
+ # Make sure stderr is unbuffered
|
|
|
+ STDERR->autoflush(1);
|
|
|
+
|
|
|
+ open(my $old_stderr, '>&', \*STDERR) or die;
|
|
|
+ $old_stderr->autoflush(1);
|
|
|
+
|
|
|
+ open(STDERR, '>', File::Spec->devnull()) or return undef;
|
|
|
+ STDERR->autoflush(1);
|
|
|
+
|
|
|
+ my $ver;
|
|
|
+ my $espok = open(my $esp, '-|', @espcmd, '--help');
|
|
|
+ if ($espok) {
|
|
|
+ while (defined(my $l = <$esp>)) {
|
|
|
+ if (!defined($ver) &&
|
|
|
+ $l =~ /\besptool\S*\s+(v\S+)/) {
|
|
|
+ $ver = $1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ close($esp);
|
|
|
+ }
|
|
|
+
|
|
|
+ open(STDERR, '>&', $old_stderr) or die;
|
|
|
+ close($old_stderr);
|
|
|
+
|
|
|
+ if ($ver) {
|
|
|
+ $esptool = $cmd;
|
|
|
+ }
|
|
|
+ return $ver;
|
|
|
+}
|
|
|
+
|
|
|
+sub updir($) {
|
|
|
+ my($dir) = @_;
|
|
|
+ return File::Spec->catdir($dir, File::Spec->updir());
|
|
|
+}
|
|
|
+
|
|
|
+sub find_esptool() {
|
|
|
+ my $ver;
|
|
|
+ my $python = '"' . ($ENV{'PYTHON'} || 'python') . '"';
|
|
|
+
|
|
|
+ # The easy variants
|
|
|
+ foreach my $et ($ENV{'ESPTOOL'}, 'esptool', 'esptool.py') {
|
|
|
+ next unless ($et ne '');
|
|
|
+ $ver = try_esptool($et);
|
|
|
+ return $ver if ($ver);
|
|
|
+ }
|
|
|
+
|
|
|
+ # More complicated
|
|
|
+ foreach my $p ($ENV{'ESPTOOL'}, File::Spec->path()) {
|
|
|
+ next unless ($p ne '');
|
|
|
+ next unless ( -d $p );
|
|
|
+ my $et = File::Spec->catfile($p, 'esptool.py');
|
|
|
+ if ( -f $et ) {
|
|
|
+ $ver = try_esptool("${python} \"${et}\"");
|
|
|
+ return $ver if ($ver);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ # Try to find it in an Arduino directory
|
|
|
+ foreach my $dp (File::HomeDir->my_data,
|
|
|
+ File::HomeDir->my_documents,
|
|
|
+ updir(File::HomeDir->my_data),
|
|
|
+ File::HomeDir->my_home) {
|
|
|
+ next unless ( -d $dp );
|
|
|
+ foreach my $dn ('.arduino15', 'Arduino15', 'ArduinoData') {
|
|
|
+ my $etr = File::Spec->catdir($dp, $dn,
|
|
|
+ qw(packages esp32 tools esptool_py));
|
|
|
+ opendir(my $dh, $etr) or next;
|
|
|
+ while (defined(my $sd = readdir($dh))) {
|
|
|
+ next if ($sd eq File::Spec->curdir);
|
|
|
+ next if ($sd eq File::Spec->updir);
|
|
|
+ my $etd = File::Spec->catdir($etr, $sd);
|
|
|
+ next unless ( -d $etd );
|
|
|
+
|
|
|
+ my $esppath = File::Spec->catfile($etd, 'esptool');
|
|
|
+ if ( ! -d $esppath ) {
|
|
|
+ $ver = try_esptool("\"$esppath\"");
|
|
|
+ last if ($ver);
|
|
|
+ }
|
|
|
+
|
|
|
+ $esppath .= '.py';
|
|
|
+ if ( -f $esppath ) {
|
|
|
+ $ver = try_esptool("${python} \"$esppath\"");
|
|
|
+ last if ($ver);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ closedir($dh);
|
|
|
+
|
|
|
+ return $ver if ($ver);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return $ver;
|
|
|
+}
|
|
|
+
|
|
|
sub run_esptool($$$$@)
|
|
|
{
|
|
|
my($port,$common_options,$cmd,$cmd_options,@args) = @_;
|
|
|
|
|
|
my @espcmd = unquote_cmd($esptool);
|
|
|
- push(@espcmd, '--port', $port);
|
|
|
+ if (defined($port) && $common_options->{'port'} ne '') {
|
|
|
+ push(@espcmd, '--port', $port);
|
|
|
+ }
|
|
|
push(@espcmd, hash2opt($common_options));
|
|
|
push(@espcmd, unquote_cmd($cmd));
|
|
|
push(@espcmd, hash2opt($cmd_options));
|
|
@@ -171,7 +280,8 @@ sub run_esptool($$$$@)
|
|
|
my $esp;
|
|
|
if (!open($esp, '-|', @espcmd)) {
|
|
|
print STDERR $!, "\n";
|
|
|
- exit 1;
|
|
|
+ exit 1 if (defined($port));
|
|
|
+ return undef;
|
|
|
}
|
|
|
|
|
|
while (defined(my $l = <$esp>)) {
|
|
@@ -198,7 +308,12 @@ sub run_esptool($$$$@)
|
|
|
|
|
|
print STDERR @output;
|
|
|
|
|
|
- die "$0: $espcmd[0] $cmd failed\n" unless ($ok);
|
|
|
+ if (!$ok) {
|
|
|
+ if (defined($port)) {
|
|
|
+ die "$0: $espcmd[0] $cmd failed\n";
|
|
|
+ }
|
|
|
+ return undef;
|
|
|
+ }
|
|
|
return %outinfo;
|
|
|
}
|
|
|
|
|
@@ -260,21 +375,36 @@ sub match_version($$)
|
|
|
my %espopt = ('before' => 'default_reset', 'after' => 'hard_reset',
|
|
|
'connect-attempts' => 8);
|
|
|
|
|
|
+my $tmpdir;
|
|
|
+# Get a filename in $tmpdir
|
|
|
+sub tmpfilename($) {
|
|
|
+ my($filename) = @_;
|
|
|
+
|
|
|
+ if (!defined($tmpdir)) {
|
|
|
+ die "$0: tmpfilename() called before tmpdir exists\n";
|
|
|
+ }
|
|
|
+
|
|
|
+ return File::Spec->catfile($tmpdir, $filename);
|
|
|
+}
|
|
|
|
|
|
-sub get_target_board($$)
|
|
|
+sub get_target_board($)
|
|
|
{
|
|
|
- my($port,$boardinfo) = @_;
|
|
|
+ my($port) = @_;
|
|
|
+
|
|
|
+ my $boardinfo = tmpfilename('boardinfo.bin');
|
|
|
|
|
|
my %myespopt = (%espopt);
|
|
|
$myespopt{'after'} = 'no_reset';
|
|
|
- run_esptool($port, \%myespopt,
|
|
|
+ run_esptool($port, { hgrep {!/^flash_/} \%myespopt },
|
|
|
'read_flash', { },
|
|
|
- ''.$boardinfo_addr, ''.$boardinfo_len, $boardinfo);
|
|
|
+ sprintf('0x%x', $boardinfo_addr),
|
|
|
+ $boardinfo_len, $boardinfo);
|
|
|
|
|
|
open(my $bi, '<:raw', $boardinfo) or return undef;
|
|
|
my $bid;
|
|
|
read($bi, $bid, $boardinfo_len);
|
|
|
close($bi);
|
|
|
+ unlink($boardinfo);
|
|
|
return undef if (length($bid) != $boardinfo_len);
|
|
|
|
|
|
my @bh = unpack('VVVVZ[256]', $bid);
|
|
@@ -293,250 +423,126 @@ sub get_target_board($$)
|
|
|
return undef;
|
|
|
}
|
|
|
|
|
|
-my @args = @ARGV;
|
|
|
-my $esponly = 0;
|
|
|
-my $file;
|
|
|
-my $target_board = undef;
|
|
|
-my $setver = 0;
|
|
|
-
|
|
|
-while (1) {
|
|
|
- $file = shift(@args);
|
|
|
- last if ($file !~ /^\-/);
|
|
|
-
|
|
|
- if ($file eq '--esponly') {
|
|
|
- $esponly = 1;
|
|
|
- } elsif ($file eq '--setver') {
|
|
|
- $target_board = shift(@args);
|
|
|
- $setver = defined($target_board);
|
|
|
- } elsif ($file eq '--') {
|
|
|
- $file = shift(@args);
|
|
|
- last;
|
|
|
- } else {
|
|
|
- undef $file; # Invalid argument, print usage
|
|
|
- last;
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-my $port = shift(@args);
|
|
|
-if (!defined($port)) {
|
|
|
- die "Usage: $0 [--esponly][--setver versionoptions] file.fw port [esptool_options...]\n";
|
|
|
-}
|
|
|
-
|
|
|
-if (!File::Spec->file_name_is_absolute($port)) {
|
|
|
- if (-c "/dev/$port") {
|
|
|
- $port = "/dev/$port";
|
|
|
- } elsif (-c "/dev/tty$port") {
|
|
|
- $port = "/dev/tty$port";
|
|
|
- } elsif ($^O eq 'MSWin32') {
|
|
|
- $port = "\\\\.\\$port";
|
|
|
- } else {
|
|
|
- die "$0: no such serial port: $port\n";
|
|
|
- }
|
|
|
-}
|
|
|
-print STDERR "Using serial port device $port\n";
|
|
|
-
|
|
|
-my $td = File::Temp->newdir(CLEANUP => 1);
|
|
|
-my $boardinfo = File::Spec->catfile($td, 'boardinfo.bin');
|
|
|
-
|
|
|
-my @espfiles = ();
|
|
|
-
|
|
|
-if (defined($target_board)) {
|
|
|
- # Forcibly write target board version
|
|
|
- if (!target_string_valid($target_board)) {
|
|
|
- die "$0: $port: invalid firmware target string: $target_board\n";
|
|
|
- }
|
|
|
-
|
|
|
- open(my $bi, '>:raw', $boardinfo)
|
|
|
- or die "$0: $port: $boardinfo: $!\n";
|
|
|
- my $bid = $target_board . "\0";
|
|
|
- $bid .= "\xff" x ($boardinfo_len - length($bid));
|
|
|
- print $bi $bid;
|
|
|
- close($bi);
|
|
|
- push(@espfiles, ''.$boardinfo_addr, $boardinfo);
|
|
|
-} else {
|
|
|
- # Get the board version from target flash
|
|
|
+sub read_fwfile($$) {
|
|
|
+ my($file, $target_board) = @_;
|
|
|
|
|
|
- $target_board = get_target_board($port, $boardinfo);
|
|
|
- if (!defined($target_board) || !target_string_valid($target_board)) {
|
|
|
- die "$0: $port: board version not programmed, specify with --setver\n";
|
|
|
- }
|
|
|
-}
|
|
|
+ return undef if (!defined($file));
|
|
|
|
|
|
-open(my $fw, '<:gzip', $file)
|
|
|
- or die "$0: $file: $!\n";
|
|
|
+ open(my $fw, '<:gzip', $file)
|
|
|
+ or die "$0: $file: $!\n";
|
|
|
|
|
|
-my @chunks = ();
|
|
|
-my $version_match = undef;
|
|
|
+ my @chunks = ();
|
|
|
+ my $version_match = undef;
|
|
|
|
|
|
-my $err = 0;
|
|
|
+ my $err = 0;
|
|
|
|
|
|
-my $hdr;
|
|
|
-while (read($fw, $hdr, 16) == 16) {
|
|
|
- # magic type flags data_len addr
|
|
|
- my @h = unpack('VvvVV', $hdr);
|
|
|
- my $c = { 'hdr' => $hdr, 'magic' => $h[0], 'type' => $h[1],
|
|
|
- 'flags' => $h[2], 'len' => $h[3], 'addr' => $h[4],
|
|
|
- 'vmatch' => 0, 'vmask' => 0, 'vmin' => 0, 'vmax' => 0xffff };
|
|
|
- if ($c->{'magic'} == $FW_MAGIC[2]) {
|
|
|
- if (read($fw, $hdr, 16) != 16) {
|
|
|
- print STDERR "$0: $file: short header read\n";
|
|
|
+ my $hdr;
|
|
|
+ while (read($fw, $hdr, 16) == 16) {
|
|
|
+ # magic type flags data_len addr
|
|
|
+ my @h = unpack('VvvVV', $hdr);
|
|
|
+ my $c = { 'hdr' => $hdr, 'magic' => $h[0], 'type' => $h[1],
|
|
|
+ 'flags' => $h[2], 'len' => $h[3], 'addr' => $h[4],
|
|
|
+ 'vmatch' => 0, 'vmask' => 0, 'vmin' => 0, 'vmax' => 0xffff };
|
|
|
+ if ($c->{'magic'} == $FW_MAGIC[2]) {
|
|
|
+ if (read($fw, $hdr, 16) != 16) {
|
|
|
+ print STDERR "$0: $file: short header read\n";
|
|
|
+ $err = 1;
|
|
|
+ last;
|
|
|
+ }
|
|
|
+ my @h = unpack('VVvvV', $hdr);
|
|
|
+ $c->{'hdr'} .= $hdr;
|
|
|
+ $c->{'vmatch'} = $h[0];
|
|
|
+ $c->{'vmask'} = $h[1];
|
|
|
+ $c->{'vmin'} = $h[2];
|
|
|
+ $c->{'vmax'} = $h[3];
|
|
|
+ } elsif ($c->{'magic'} != $FW_MAGIC[1]) {
|
|
|
+ print STDERR "$0: $file: bad chunk magic\n";
|
|
|
$err = 1;
|
|
|
last;
|
|
|
}
|
|
|
- my @h = unpack('VVvvV', $hdr);
|
|
|
- $c->{'hdr'} .= $hdr;
|
|
|
- $c->{'vmatch'} = $h[0];
|
|
|
- $c->{'vmask'} = $h[1];
|
|
|
- $c->{'vmin'} = $h[2];
|
|
|
- $c->{'vmax'} = $h[3];
|
|
|
- } elsif ($c->{'magic'} != $FW_MAGIC[1]) {
|
|
|
- print STDERR "$0: $file: bad chunk magic\n";
|
|
|
- $err = 1;
|
|
|
- last;
|
|
|
- }
|
|
|
-
|
|
|
- my $t = $type[$c->{'type'}];
|
|
|
|
|
|
- my $d;
|
|
|
- if (read($fw, $d, $c->{'len'}) != $c->{'len'}) {
|
|
|
- print STDERR "$0: $file: short chunk read\n";
|
|
|
- $err = 1;
|
|
|
- last;
|
|
|
- }
|
|
|
- $c->{'data'} = $d;
|
|
|
+ my $t = $type[$c->{'type'}];
|
|
|
|
|
|
- if ($t eq 'target') {
|
|
|
- my $is_match = match_version($target_board, $d);
|
|
|
- if ($c->{'magic'} == $FW_MAGIC[1] || $is_match) {
|
|
|
- $version_match = $c;
|
|
|
- }
|
|
|
- print STDERR "$0: $file: supports hardware: $d",
|
|
|
- ($is_match ? ' (match)' : ''), "\n";
|
|
|
- } elsif ($t eq 'end' || $t eq 'note' || ($c->{'flags'} & $FDF_PRETARGET)) {
|
|
|
- # Not filtered
|
|
|
- } else {
|
|
|
- if (!defined($version_match)) {
|
|
|
- print STDERR "$0: $file: hardware version $target_board not supported\n";
|
|
|
+ my $d;
|
|
|
+ if (read($fw, $d, $c->{'len'}) != $c->{'len'}) {
|
|
|
+ print STDERR "$0: $file: short chunk read\n";
|
|
|
$err = 1;
|
|
|
last;
|
|
|
}
|
|
|
+ $c->{'data'} = $d;
|
|
|
|
|
|
- if ($c->{'vmin'} > $version_match->{'vmax'} ||
|
|
|
- $c->{'vmax'} < $version_match->{'vmin'} ||
|
|
|
- (($c->{'vmatch'} ^ $version_match->{'vmatch'}) & $c->{'vmask'})) {
|
|
|
- # Not applicable to this board
|
|
|
- next;
|
|
|
+ if ($t eq 'target') {
|
|
|
+ my $is_match = match_version($target_board, $d);
|
|
|
+ if ($c->{'magic'} == $FW_MAGIC[1] || $is_match) {
|
|
|
+ $version_match = $c;
|
|
|
+ }
|
|
|
+ print STDERR "$0: $file: supports hardware: $d",
|
|
|
+ ($is_match ? ' (match)' : ''), "\n";
|
|
|
+ } elsif ($t eq 'end' || $t eq 'note' || ($c->{'flags'} & $FDF_PRETARGET)) {
|
|
|
+ # Not filtered
|
|
|
+ } else {
|
|
|
+ if (!defined($version_match)) {
|
|
|
+ print STDERR "$0: $file: hardware version $target_board not supported\n";
|
|
|
+ $err = 1;
|
|
|
+ last;
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($c->{'vmin'} > $version_match->{'vmax'} ||
|
|
|
+ $c->{'vmax'} < $version_match->{'vmin'} ||
|
|
|
+ (($c->{'vmatch'} ^ $version_match->{'vmatch'}) & $c->{'vmask'})) {
|
|
|
+ # Not applicable to this board
|
|
|
+ next;
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- push(@chunks, $c);
|
|
|
+ push(@chunks, $c);
|
|
|
|
|
|
- last if ($t eq 'end'); # End of stream
|
|
|
-}
|
|
|
+ last if ($t eq 'end'); # End of stream
|
|
|
+ }
|
|
|
|
|
|
-close($fw);
|
|
|
-exit $err if ($err);
|
|
|
+ close($fw);
|
|
|
|
|
|
-%espopt = (%espopt,
|
|
|
- 'baud' => 115200, 'port' => undef, 'chip' => undef,
|
|
|
- 'flash_mode' => undef, 'flash_freq' => undef,
|
|
|
- 'flash_size' => undef);
|
|
|
+ exit 1 if ($err);
|
|
|
+
|
|
|
+ # Sort out the ESP chunks as files for esptool versus the ones
|
|
|
+ # that should be uploaded once the ESP software is running
|
|
|
+ my @espfiles;
|
|
|
+ my $raw_fpgadata;
|
|
|
|
|
|
-# Create a compressed data buffer without the ESP32 chunks
|
|
|
-my $fpgadata;
|
|
|
-open(my $fpgafh, '>:gzip', \$fpgadata) or die;
|
|
|
-
|
|
|
-my $nc = 0;
|
|
|
-foreach my $c ( @chunks ) {
|
|
|
- my $t = $type[$c->{'type'}];
|
|
|
- if ($t eq 'esptool') {
|
|
|
- my $s = $c->{'data'};
|
|
|
- $s =~ s/[\r\n]+/ /g;
|
|
|
- while ($s =~ /^\s*(\S+)\s+(\S+)(.*)/) {
|
|
|
- $espopt{$1} = $2;
|
|
|
- $s = $3;
|
|
|
+ my $nc = 0;
|
|
|
+ foreach my $c ( @chunks ) {
|
|
|
+ my $t = $type[$c->{'type'}];
|
|
|
+ if ($t eq 'esptool') {
|
|
|
+ my $s = $c->{'data'};
|
|
|
+ $s =~ s/[\r\n]+/ /g;
|
|
|
+ while ($s =~ /^\s*(\S+)\s+(\S+)(.*)/) {
|
|
|
+ $espopt{$1} = $2;
|
|
|
+ $s = $3;
|
|
|
+ }
|
|
|
+ } elsif ($t =~ /^esp/ && $c->{'addr'}) {
|
|
|
+ my $addr = sprintf('%x', $c->{'addr'});
|
|
|
+ my $tff = tmpfilename($t.$nc.'_'.$addr.'.bin');
|
|
|
+ open(my $tf, '>', $tff) or die "$0: $tff: $!\n";
|
|
|
+ print $tf $c->{'data'};
|
|
|
+ close($tf);
|
|
|
+ push(@espfiles, [$c->{'addr'}, $tff]);
|
|
|
+ } elsif ($t eq 'note' || ($t eq 'target' && $c != $version_match)) {
|
|
|
+ # Skip
|
|
|
+ } else {
|
|
|
+ $raw_fpgadata .= $c->{'hdr'};
|
|
|
+ $raw_fpgadata .= $c->{'data'};
|
|
|
}
|
|
|
- } elsif ($t =~ /^esp/ && $c->{'addr'}) {
|
|
|
- my $addr = sprintf('%x', $c->{'addr'});
|
|
|
- my $tff = File::Spec->catfile($td, $t.$nc.'_'.$addr.'.bin');
|
|
|
- open(my $tf, '>', $tff) or die "$0: $tff: $!\n";
|
|
|
- print $tf $c->{'data'};
|
|
|
- close($tf);
|
|
|
- push(@espfiles, '0x'.$addr, $tff);
|
|
|
- } elsif ($t eq 'note' || ($t eq 'target' && $c != $version_match)) {
|
|
|
- # Skip
|
|
|
- } else {
|
|
|
- print $fpgafh $c->{'hdr'};
|
|
|
- print $fpgafh $c->{'data'};
|
|
|
+ $nc++;
|
|
|
}
|
|
|
- $nc++;
|
|
|
-}
|
|
|
|
|
|
-close($fpgafh);
|
|
|
-
|
|
|
-foreach my $e (keys %espopt) {
|
|
|
- my $ev = $ENV{"ESP\U$e"};
|
|
|
- if (defined($ev)) {
|
|
|
- $espopt{$e} = $ev;
|
|
|
+ my $fpgadata;
|
|
|
+ if (defined($raw_fpgadata)) {
|
|
|
+ open(my $fpgafh, '>:gzip', \$fpgadata) or die;
|
|
|
+ print $fpgafh $raw_fpgadata;
|
|
|
+ close($fpgafh);
|
|
|
}
|
|
|
-}
|
|
|
|
|
|
-run_esptool($port, { hgrep {!/^flash_/} %espopt },
|
|
|
- 'write_flash', { hgrep {/^flash_/} %espopt },
|
|
|
- '-z', @espfiles);
|
|
|
-
|
|
|
-my $SerialPort = eval {
|
|
|
- require Device::SerialPort;
|
|
|
- Device::SerialPort->import(qw(:PARAM));
|
|
|
- 'Device::SerialPort';
|
|
|
-} || eval {
|
|
|
- require Win32::SerialPort;
|
|
|
- Win32::SerialPort->import(qw(:STAT));
|
|
|
- 'Win32::SerialPort';
|
|
|
-} || die "$0: need Device::SerialPort or Win32::SerialPort\n";
|
|
|
-
|
|
|
-print STDERR "Waiting for reinit...\n";
|
|
|
-usleep(4000000);
|
|
|
-
|
|
|
-my $tty = $SerialPort->new($port);
|
|
|
-die "$0: $port: $!\n" if (!$tty);
|
|
|
-
|
|
|
-$tty->buffers($tty->buffer_max);
|
|
|
-$tty->reset_error;
|
|
|
-
|
|
|
-$tty->user_msg(1);
|
|
|
-$tty->error_msg(1);
|
|
|
-
|
|
|
-$tty->handshake('none');
|
|
|
-$tty->databits(8);
|
|
|
-$tty->baudrate(115200);
|
|
|
-$tty->stopbits(1);
|
|
|
-$tty->parity('none');
|
|
|
-$tty->stty_istrip(0);
|
|
|
-$tty->stty_inpck(0);
|
|
|
-$tty->datatype('raw');
|
|
|
-$tty->stty_icanon(0);
|
|
|
-$tty->stty_opost(0);
|
|
|
-$tty->stty_ignbrk(0);
|
|
|
-$tty->stty_inlcr(0);
|
|
|
-$tty->stty_igncr(0);
|
|
|
-$tty->stty_icrnl(0);
|
|
|
-$tty->stty_echo(0);
|
|
|
-$tty->stty_echonl(0);
|
|
|
-$tty->stty_isig(0);
|
|
|
-$tty->read_char_time(0);
|
|
|
-$tty->write_settings;
|
|
|
-
|
|
|
-# In case DTR and/or RTS was asserted on the physical serial port.
|
|
|
-# Note that DTR needs to be deasserted before RTS!
|
|
|
-# However, deasserting DTR on the ACM port prevents it from working,
|
|
|
-# so only do this if we don't see CTS (which is always enabled on ACM)...
|
|
|
-if ($tty->can_modemlines && !($tty->modemlines & $tty->MS_CTS_ON)) {
|
|
|
- usleep(100000);
|
|
|
- $tty->dtr_active(0);
|
|
|
- usleep(100000);
|
|
|
- $tty->rts_active(0);
|
|
|
- usleep(100000);
|
|
|
+ return ([@espfiles], $fpgadata);
|
|
|
}
|
|
|
|
|
|
sub tty_read {
|
|
@@ -559,7 +565,6 @@ sub tty_read {
|
|
|
|
|
|
return $r;
|
|
|
}
|
|
|
-my $ttybuf = '';
|
|
|
|
|
|
sub tty_write($$) {
|
|
|
my($tty,$data) = @_;
|
|
@@ -576,139 +581,333 @@ sub tty_write($$) {
|
|
|
return $offs;
|
|
|
}
|
|
|
|
|
|
-my $found = 0;
|
|
|
-my $tt = time();
|
|
|
-my $start_enq = $tt;
|
|
|
-tty_write($tty, "\005"); # ENQ
|
|
|
-while (($tt = time()) - $start_enq < 30) {
|
|
|
- my $d = tty_read($tty, \$ttybuf, 1000);
|
|
|
- if ($d eq '') {
|
|
|
- tty_write($tty, "\005"); # ENQ
|
|
|
- } else {
|
|
|
- my $ix = index("\026\004\027", $d);
|
|
|
- if ($ix < 0) {
|
|
|
- print STDERR $d;
|
|
|
- next;
|
|
|
+sub upload_fpgadata($$) {
|
|
|
+ my($port, $fpgadata) = @_;
|
|
|
+
|
|
|
+ my $SerialPort = eval {
|
|
|
+ require Device::SerialPort;
|
|
|
+ Device::SerialPort->import(qw(:PARAM));
|
|
|
+ 'Device::SerialPort';
|
|
|
+ } || eval {
|
|
|
+ require Win32::SerialPort;
|
|
|
+ Win32::SerialPort->import(qw(:STAT));
|
|
|
+ 'Win32::SerialPort';
|
|
|
+ } || die "$0: need Device::SerialPort or Win32::SerialPort\n";
|
|
|
+
|
|
|
+ print STDERR "Waiting for reinit...\n";
|
|
|
+ usleep(4000000);
|
|
|
+
|
|
|
+ my $tty = $SerialPort->new($port);
|
|
|
+ die "$0: $port: $!\n" if (!$tty);
|
|
|
+
|
|
|
+ $tty->buffers($tty->buffer_max);
|
|
|
+ $tty->reset_error;
|
|
|
+
|
|
|
+ $tty->user_msg(1);
|
|
|
+ $tty->error_msg(1);
|
|
|
+
|
|
|
+ $tty->handshake('none');
|
|
|
+ $tty->databits(8);
|
|
|
+ $tty->baudrate(115200);
|
|
|
+ $tty->stopbits(1);
|
|
|
+ $tty->parity('none');
|
|
|
+ $tty->stty_istrip(0);
|
|
|
+ $tty->stty_inpck(0);
|
|
|
+ $tty->datatype('raw');
|
|
|
+ $tty->stty_icanon(0);
|
|
|
+ $tty->stty_opost(0);
|
|
|
+ $tty->stty_ignbrk(0);
|
|
|
+ $tty->stty_inlcr(0);
|
|
|
+ $tty->stty_igncr(0);
|
|
|
+ $tty->stty_icrnl(0);
|
|
|
+ $tty->stty_echo(0);
|
|
|
+ $tty->stty_echonl(0);
|
|
|
+ $tty->stty_isig(0);
|
|
|
+ $tty->read_char_time(0);
|
|
|
+ $tty->write_settings;
|
|
|
+
|
|
|
+ # In case DTR and/or RTS was asserted on the physical serial port.
|
|
|
+ # Note that DTR needs to be deasserted before RTS!
|
|
|
+ # However, deasserting DTR on the ACM port prevents it from working,
|
|
|
+ # so only do this if we don't see CTS (which is always enabled on ACM)...
|
|
|
+ if ($tty->can_modemlines && !($tty->modemlines & $tty->MS_CTS_ON)) {
|
|
|
+ usleep(100000);
|
|
|
+ $tty->dtr_active(0);
|
|
|
+ usleep(100000);
|
|
|
+ $tty->rts_active(0);
|
|
|
+ usleep(100000);
|
|
|
+ }
|
|
|
+
|
|
|
+ my $ttybuf = '';
|
|
|
+
|
|
|
+
|
|
|
+ my $found = 0;
|
|
|
+ my $tt = time();
|
|
|
+ my $start_enq = $tt;
|
|
|
+ tty_write($tty, "\005"); # ENQ
|
|
|
+ while (($tt = time()) - $start_enq < 30) {
|
|
|
+ my $d = tty_read($tty, \$ttybuf, 1000);
|
|
|
+ if ($d eq '') {
|
|
|
+ tty_write($tty, "\005"); # ENQ
|
|
|
+ } else {
|
|
|
+ my $ix = index("\026\004\027", $d);
|
|
|
+ if ($ix < 0) {
|
|
|
+ print STDERR $d;
|
|
|
+ next;
|
|
|
+ }
|
|
|
+ $found = $found == $ix ? $found+1 : 0;
|
|
|
+ last if ($found == 3);
|
|
|
}
|
|
|
- $found = $found == $ix ? $found+1 : 0;
|
|
|
- last if ($found == 3);
|
|
|
}
|
|
|
-}
|
|
|
|
|
|
-if ($found < 3) {
|
|
|
- die "$0: $port: no MAX80 ESP32 detected\n";
|
|
|
-}
|
|
|
+ if ($found < 3) {
|
|
|
+ die "$0: $port: no MAX80 ESP32 detected\n";
|
|
|
+ }
|
|
|
|
|
|
-$start_enq = $tt;
|
|
|
-my $last_req;
|
|
|
-my $winspc;
|
|
|
-while (!defined($winspc)) {
|
|
|
- $tt = time();
|
|
|
- if ($tt - $start_enq >= 10) {
|
|
|
- die "$0: $port: failed to start FPGA firmware upload\n";
|
|
|
+ $start_enq = $tt;
|
|
|
+ my $last_req;
|
|
|
+ my $winspc;
|
|
|
+ while (!defined($winspc)) {
|
|
|
+ $tt = time();
|
|
|
+ if ($tt - $start_enq >= 10) {
|
|
|
+ die "$0: $port: failed to start FPGA firmware upload\n";
|
|
|
+ }
|
|
|
+ if ($tt != $last_req) {
|
|
|
+ tty_write($tty, "\034\001: /// MAX80 FW UPLOAD \~\@\~ \$\r\n\035");
|
|
|
+ $last_req = $tt;
|
|
|
+ }
|
|
|
+ my $d;
|
|
|
+ while (1) {
|
|
|
+ $d = tty_read($tty, \$ttybuf, 100);
|
|
|
+ last if ($d eq '');
|
|
|
+ my $dc = unpack('C', $d);
|
|
|
+ print STDERR $a[$dc];
|
|
|
+ if ($dc == 036) {
|
|
|
+ $winspc = 0;
|
|
|
+ last;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
- if ($tt != $last_req) {
|
|
|
- tty_write($tty, "\034\001: /// MAX80 FW UPLOAD \~\@\~ \$\r\n\035");
|
|
|
- $last_req = $tt;
|
|
|
+
|
|
|
+ my $bytes = length($fpgadata);
|
|
|
+ my $offset = 0;
|
|
|
+ my $maxchunk = 64;
|
|
|
+ my $maxahead = 256;
|
|
|
+ my $last_ack = 0;
|
|
|
+ my @pktends = ();
|
|
|
+ my $last_enq = 0;
|
|
|
+ my $last_ack_time = 0;
|
|
|
+
|
|
|
+ print STDERR "\nStarting packet transmit...\n";
|
|
|
+
|
|
|
+ while ($last_ack < $bytes) {
|
|
|
+ my $chunk;
|
|
|
+ my $now;
|
|
|
+
|
|
|
+ while (1) {
|
|
|
+ $chunk = $bytes - $offset;
|
|
|
+ $chunk = $winspc if ($chunk > $winspc);
|
|
|
+ if ($offset + $chunk > $last_ack + $maxahead) {
|
|
|
+ $chunk = $last_ack + $maxahead - $offset;
|
|
|
+ }
|
|
|
+ $chunk = 0 if ($chunk <= 0);
|
|
|
+ $chunk = $maxchunk if ($chunk > $maxchunk);
|
|
|
+
|
|
|
+ my $d = tty_read($tty, \$ttybuf, $chunk ? 0 : $maxchunk/10);
|
|
|
+ $now = time();
|
|
|
+ last if ($d eq '');
|
|
|
+
|
|
|
+ my $dc = unpack('C', $d);
|
|
|
+ if ($dc == 022) {
|
|
|
+ $winspc = 0;
|
|
|
+ } elsif ($dc == 024) {
|
|
|
+ if (defined($winspc)) {
|
|
|
+ $winspc += 256;
|
|
|
+ }
|
|
|
+ } elsif ($dc == 006) {
|
|
|
+ $last_ack = shift(@pktends) || $last_ack;
|
|
|
+ if ($now != $last_ack_time) {
|
|
|
+ printf STDERR "%s: %s: %d/%d (%d%%) uploaded\n",
|
|
|
+ $0, $port, $last_ack, $bytes, $last_ack*100/$bytes;
|
|
|
+ $last_ack_time = $now;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ print STDERR $a[$dc];
|
|
|
+ if ($dc == 025 || $dc == 031 || $dc == 037) {
|
|
|
+ $offset = $last_ack;
|
|
|
+ @pktends = ();
|
|
|
+ undef $winspc; # Wait for WRST before resuming
|
|
|
+ } elsif ($dc == 030) {
|
|
|
+ print STDERR "\n";
|
|
|
+ die "$0: $port: upload aborted by target system\n";
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!$chunk) {
|
|
|
+ if ($bytes > $offset) {
|
|
|
+ if ($now != $last_enq) {
|
|
|
+ tty_write($tty, "\026"); # SYN: request window resync
|
|
|
+ $last_enq = $now;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ next;
|
|
|
+ }
|
|
|
+
|
|
|
+ my $data = substr($fpgadata, $offset, $chunk);
|
|
|
+ my $hdr = pack("CvVV", $chunk-1, 0, $offset, crc32($data));
|
|
|
+
|
|
|
+ tty_write($tty, "\002".mybaseencode($hdr, $data));
|
|
|
+
|
|
|
+ push(@pktends, $offset + $chunk);
|
|
|
+ $offset += $chunk;
|
|
|
+ $winspc -= $chunk;
|
|
|
}
|
|
|
- my $d;
|
|
|
+
|
|
|
+ tty_write($tty, "\004"); # EOT
|
|
|
+
|
|
|
+ # Final messages out
|
|
|
while (1) {
|
|
|
- $d = tty_read($tty, \$ttybuf, 100);
|
|
|
+ my $d = tty_read($tty, \$ttybuf, 1000);
|
|
|
last if ($d eq '');
|
|
|
- my $dc = unpack('C', $d);
|
|
|
- print STDERR $a[$dc];
|
|
|
- if ($dc == 036) {
|
|
|
- $winspc = 0;
|
|
|
- last;
|
|
|
- }
|
|
|
+ print STDERR $d;
|
|
|
}
|
|
|
+ $tty->close;
|
|
|
+
|
|
|
+ return 0;
|
|
|
}
|
|
|
|
|
|
-my $bytes = length($fpgadata);
|
|
|
-my $offset = 0;
|
|
|
-my $maxchunk = 64;
|
|
|
-my $maxahead = 256;
|
|
|
-my $last_ack = 0;
|
|
|
-my @pktends = ();
|
|
|
-my $last_enq = 0;
|
|
|
-my $last_ack_time = 0;
|
|
|
+my @args = @ARGV;
|
|
|
+my $esponly = 0;
|
|
|
+my $file;
|
|
|
+my $target_board = undef;
|
|
|
+my $setver = 0;
|
|
|
+my $port = undef;
|
|
|
+my $which = 0;
|
|
|
|
|
|
-print STDERR "\nStarting packet transmit...\n";
|
|
|
+while (1) {
|
|
|
+ $file = shift(@args);
|
|
|
+ last if ($file !~ /^\-/);
|
|
|
|
|
|
-while ($last_ack < $bytes) {
|
|
|
- my $chunk;
|
|
|
- my $now;
|
|
|
+ if ($file eq '--esponly') {
|
|
|
+ $esponly = 1;
|
|
|
+ } elsif ($file eq '--which') {
|
|
|
+ $which = 1;
|
|
|
+ } elsif ($file eq '--setver') {
|
|
|
+ $target_board = shift(@args);
|
|
|
+ $setver = defined($target_board);
|
|
|
+ } elsif ($file eq '--port') {
|
|
|
+ $port = shift(@args);
|
|
|
+ } elsif ($file eq '--') {
|
|
|
+ $file = shift(@args);
|
|
|
+ last;
|
|
|
+ } else {
|
|
|
+ undef $file; # Invalid argument, print usage
|
|
|
+ last;
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
- while (1) {
|
|
|
- $chunk = $bytes - $offset;
|
|
|
- $chunk = $winspc if ($chunk > $winspc);
|
|
|
- if ($offset + $chunk > $last_ack + $maxahead) {
|
|
|
- $chunk = $last_ack + $maxahead - $offset;
|
|
|
- }
|
|
|
- $chunk = 0 if ($chunk <= 0);
|
|
|
- $chunk = $maxchunk if ($chunk > $maxchunk);
|
|
|
+# Legacy command line support: if no --port, specify port after firmware file
|
|
|
+if (defined($file) && !defined($port) && $args[0] ne '--esptool') {
|
|
|
+ $port = shift(@args);
|
|
|
+}
|
|
|
|
|
|
- my $d = tty_read($tty, \$ttybuf, $chunk ? 0 : $maxchunk/10);
|
|
|
- $now = time();
|
|
|
- last if ($d eq '');
|
|
|
+$tmpdir = File::Temp->newdir(CLEANUP => 1);
|
|
|
+if (!defined($tmpdir)) {
|
|
|
+ die "$0: failed to create temporary directory: $!\n"
|
|
|
+}
|
|
|
|
|
|
- my $dc = unpack('C', $d);
|
|
|
- if ($dc == 022) {
|
|
|
- $winspc = 0;
|
|
|
- } elsif ($dc == 024) {
|
|
|
- if (defined($winspc)) {
|
|
|
- $winspc += 256;
|
|
|
- }
|
|
|
- } elsif ($dc == 006) {
|
|
|
- $last_ack = shift(@pktends) || $last_ack;
|
|
|
- if ($now != $last_ack_time) {
|
|
|
- printf STDERR "%s: %s: %d/%d (%d%%) uploaded\n",
|
|
|
- $0, $port, $last_ack, $bytes, $last_ack*100/$bytes;
|
|
|
- $last_ack_time = $now;
|
|
|
- }
|
|
|
- } else {
|
|
|
- print STDERR $a[$dc];
|
|
|
- if ($dc == 025 || $dc == 031 || $dc == 037) {
|
|
|
- $offset = $last_ack;
|
|
|
- @pktends = ();
|
|
|
- undef $winspc; # Wait for WRST before resuming
|
|
|
- } elsif ($dc == 030) {
|
|
|
- print STDERR "\n";
|
|
|
- die "$0: $port: upload aborted by target system\n";
|
|
|
- }
|
|
|
- }
|
|
|
+my $espver = find_esptool();
|
|
|
+if (!$espver) {
|
|
|
+ die "$0: cannot find esptool, please set ESPTOOL in the environment\n";
|
|
|
+}
|
|
|
+
|
|
|
+if ($which) {
|
|
|
+ print "esptool ${espver}\n";
|
|
|
+ print $esptool, "\n";
|
|
|
+ exit 0;
|
|
|
+}
|
|
|
+
|
|
|
+print STDERR "esptool v${espver} found\n\n";
|
|
|
+
|
|
|
+if (!defined($port)) {
|
|
|
+ die "Usage: $0 [--which][--esponly][--setver version][--port port]\n".
|
|
|
+ " [file.fw] [--esptool esptool_options...]\n";
|
|
|
+}
|
|
|
+
|
|
|
+if (!File::Spec->file_name_is_absolute($port)) {
|
|
|
+ if (-c "/dev/$port") {
|
|
|
+ $port = "/dev/$port";
|
|
|
+ } elsif (-c "/dev/tty$port") {
|
|
|
+ $port = "/dev/tty$port";
|
|
|
+ } elsif ($^O eq 'MSWin32') {
|
|
|
+ $port = "\\\\.\\$port";
|
|
|
+ } else {
|
|
|
+ die "$0: no such serial port: $port\n";
|
|
|
}
|
|
|
+}
|
|
|
+print STDERR "Using serial port device $port\n";
|
|
|
|
|
|
- if (!$chunk) {
|
|
|
- if ($bytes > $offset) {
|
|
|
- if ($now != $last_enq) {
|
|
|
- tty_write($tty, "\026"); # SYN: request window resync
|
|
|
- $last_enq = $now;
|
|
|
- }
|
|
|
- }
|
|
|
- next;
|
|
|
+%espopt = (%espopt,
|
|
|
+ 'baud' => 115200, 'port' => undef, 'chip' => undef,
|
|
|
+ 'flash_mode' => undef, 'flash_freq' => undef,
|
|
|
+ 'flash_size' => undef);
|
|
|
+
|
|
|
+foreach my $e (keys %espopt) {
|
|
|
+ my $ev = $ENV{"ESP\U$e"};
|
|
|
+ if (defined($ev)) {
|
|
|
+ $espopt{$e} = $ev;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+while (defined(my $eo = shift(@args))) {
|
|
|
+ if ($eo =~ /^([^=]+)=(.*)$/) {
|
|
|
+ $espopt{$1} = $2;
|
|
|
+ } elsif ($args[0] !~ /^-/) {
|
|
|
+ $espopt{$eo} = shift(@args);
|
|
|
+ } else {
|
|
|
+ $espopt{$eo} = '';
|
|
|
}
|
|
|
+}
|
|
|
|
|
|
- my $data = substr($fpgadata, $offset, $chunk);
|
|
|
- my $hdr = pack("CvVV", $chunk-1, 0, $offset, crc32($data));
|
|
|
+my @espfiles = ();
|
|
|
|
|
|
- tty_write($tty, "\002".mybaseencode($hdr, $data));
|
|
|
+if (defined($target_board)) {
|
|
|
+ # Forcibly write target board version
|
|
|
+ if (!target_string_valid($target_board)) {
|
|
|
+ die "$0: $port: invalid firmware target string: $target_board\n";
|
|
|
+ }
|
|
|
|
|
|
- push(@pktends, $offset + $chunk);
|
|
|
- $offset += $chunk;
|
|
|
- $winspc -= $chunk;
|
|
|
+ my $boardinfo = tmpfilename('boardinfo.bin');
|
|
|
+ open(my $bi, '>:raw', $boardinfo)
|
|
|
+ or die "$0: $port: $boardinfo: $!\n";
|
|
|
+ my $bid = $target_board . "\0";
|
|
|
+ $bid .= "\xff" x ($boardinfo_len - length($bid));
|
|
|
+ print $bi $bid;
|
|
|
+ close($bi);
|
|
|
+ push(@espfiles, [$boardinfo_addr, $boardinfo]);
|
|
|
+} else {
|
|
|
+ # Get the board version from target flash
|
|
|
+
|
|
|
+ $target_board = get_target_board($port);
|
|
|
+ if (!defined($target_board) || !target_string_valid($target_board)) {
|
|
|
+ die "$0: $port: board version not programmed, specify with --setver\n";
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-tty_write($tty, "\004"); # EOT
|
|
|
+my($fwfiles, $fpgadata) = read_fwfile($file, $target_board);
|
|
|
+push(@espfiles, @$fwfiles);
|
|
|
|
|
|
-# Final messages out
|
|
|
-while (1) {
|
|
|
- my $d = tty_read($tty, \$ttybuf, 1000);
|
|
|
- last if ($d eq '');
|
|
|
- print STDERR $d;
|
|
|
+if (scalar(@espfiles)) {
|
|
|
+ run_esptool($port, { hgrep {!/^flash_/} %espopt },
|
|
|
+ 'write_flash', { hgrep {/^flash_/} %espopt },
|
|
|
+ '-z', map { (sprintf('0x%x', $_->[0]), $_->[1]) } @espfiles);
|
|
|
+}
|
|
|
+
|
|
|
+if (defined($fpgadata)) {
|
|
|
+ upload_fpgadata($port, $fpgadata);
|
|
|
}
|
|
|
-$tty->close;
|
|
|
|
|
|
-print STDERR "\n$0: $port: firmware upload complete\n";
|
|
|
+print STDERR "\n$0: $port: update complete\n";
|
|
|
|
|
|
exit 0;
|