Skip to content

Commit

Permalink
Various improvemts in ESP32 flasher tool (#9)
Browse files Browse the repository at this point in the history
  • Loading branch information
MatthiasJentsch authored and josesimoes committed May 18, 2018
1 parent 0d44bab commit f2e0129
Show file tree
Hide file tree
Showing 13 changed files with 1,272 additions and 144 deletions.
3 changes: 3 additions & 0 deletions EspFirmwareFlasher/App.config
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<!-- ********************************** -->
<!-- default ESP chip type; ESP32 and ESP8266 are allowed -->
<!--<add key="DefaultEspChipType" value="ESP32"/>-->
<!--<add key="DefaultEspChipType" value="ESP8266"/>-->
<!-- default serial port if nothing is delivered via command line argument -->
<!--<add key="DefaultSerialPort" value="COM1"/>-->
<!-- default baudrate for the serial port if nothing is delivered via command line argument -->
Expand All @@ -22,8 +23,10 @@
<!-- ******************************* -->
<!-- default firmware type -->
<!--<add key="DefaultFirmwareType" value="nanoClr"/>-->
<!--<add key="DefaultFirmwareType" value="WifiWaterLevelGauge"/>-->
<!-- default download location on bintray.com -->
<!--<add key="DefaultDownloadSource" value="https://bintray.com/nfbot/nanoframework-images-dev"/>-->
<!--<add key="DefaultDownloadSource" value="https://github.com/MatthiasJentsch/WifiWaterLevelGauge"/>-->
<!-- default board type for the firmware download -->
<!--<add key="DefaultBoardType" value="ESP32_DEVKITC"/>-->
</appSettings>
Expand Down
672 changes: 672 additions & 0 deletions EspFirmwareFlasher/Arguments.cs

Large diffs are not rendered by default.

18 changes: 15 additions & 3 deletions EspFirmwareFlasher/EspFirmwareFlasher.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,14 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Arguments.cs" />
<Compile Include="EspTool.cs" />
<Compile Include="NanoClrFirmware.cs" />
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Include="WifiWaterLevelGaugeFirmware.cs" />
<Compile Include="Firmware.cs" />
<Compile Include="Program.cs" />
Expand All @@ -56,9 +62,15 @@
<None Include="CreateEspToolZip.targets">
<SubType>Designer</SubType>
</None>
<None Include="esptool.zip">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="esptool.zip" />
<None Include="LICENSE" />
<None Include="README.md" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="CreateEspToolZip.targets" />
Expand Down
206 changes: 167 additions & 39 deletions EspFirmwareFlasher/EspTool.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
using System;
//
// Copyright (c) 2018 The nanoFramework project contributors
// See LICENSE file in the project root for full license information.
//

using EspFirmwareFlasher.Properties;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
Expand Down Expand Up @@ -62,22 +68,22 @@ internal struct Info
internal Version ToolVersion { get; private set; }

/// <summary>
/// Name of the ESP32 chip
/// Name of the ESP32/ESP8266 chip
/// </summary>
internal string ChipName { get; private set; }

/// <summary>
/// ESP32 chip features
/// ESP32/ESP8266 chip features
/// </summary>
internal string ChipFeatures { get; private set; }

/// <summary>
/// ID of the ESP32 chip
/// ID of the ESP32/ESP8266 chip
/// </summary>
internal long ChipId { get; private set; }

/// <summary>
/// MAC address of the ESP32 chip
/// MAC address of the ESP32/ESP8266 chip
/// </summary>
internal PhysicalAddress ChipMacAddress { get; private set; }

Expand All @@ -100,13 +106,13 @@ internal struct Info
/// Constructor
/// </summary>
/// <param name="toolVersion">Version of the esptool.py</param>
/// <param name="chipName">Name of the ESP32 chip</param>
/// <param name="chipFeatures">ESP32 chip features</param>
/// <param name="chipId">ID of the ESP32 chip</param>
/// <param name="chipMacAddress">MAC address of the ESP32 chip</param>
/// <param name="chipName">Name of the ESP32/ESP8266 chip</param>
/// <param name="chipFeatures">ESP32/ESP8266 chip features</param>
/// <param name="chipId">ID of the ESP32/ESP8266 chip</param>
/// <param name="chipMacAddress">MAC address of the ESP32/ESP8266 chip</param>
/// <param name="flashManufacturerId">Flash manufacturer ID: See http://code.coreboot.org/p/flashrom/source/tree/HEAD/trunk/flashchips.h for more details</param>
/// <param name="flashDeviceModelId">Flash device type ID: See http://code.coreboot.org/p/flashrom/source/tree/HEAD/trunk/flashchips.h for more details</param>
/// <param name="flashSize">The size of the flash in bytes; 4 MB = 0x40000 bytes</param>
/// <param name="flashSize">The size of the flash in bytes; e.g. 4 MB = 0x40000 bytes</param>
internal Info(Version toolVersion, string chipName, string chipFeatures, long chipId, PhysicalAddress chipMacAddress, byte flashManufacturerId, short flashDeviceModelId, int flashSize)
{
ToolVersion = toolVersion;
Expand Down Expand Up @@ -153,20 +159,19 @@ internal EspTool(string serialPort, int baudRate, string chipType, string flashM
if (!Directory.Exists("esptool"))
{
Console.WriteLine("Extracting esptool ...");
File.WriteAllBytes("esptool.zip", Resources.esptool);
ZipFile.ExtractToDirectory("esptool.zip", "esptool");
}
}

/// <summary>
/// Tests the connection to the ESP32 chip
/// Tests the connection to the ESP32/ESP8266 chip
/// </summary>
/// <returns>The filled info structure with all the information about the connected ESP32 chip or null if an error occured</returns>
/// <returns>The filled info structure with all the information about the connected ESP32/ESP8266 chip or null if an error occured</returns>
internal Info? TestChip()
{
string messages;

// execute chip_id command and parse the result
if (!RunEspTool("chip_id", out messages))
if (!RunEspTool("chip_id", true, null, out string messages))
{
Console.WriteLine(messages);
return null;
Expand All @@ -186,7 +191,7 @@ internal EspTool(string serialPort, int baudRate, string chipType, string flashM
Console.WriteLine($"Found {name} with ID {id} and features {features}");

// execute read_mac command and parse the result
if (!RunEspTool("read_mac", out messages))
if (!RunEspTool("read_mac", true, null, out messages))
{
Console.WriteLine(messages);
return null;
Expand All @@ -202,7 +207,7 @@ internal EspTool(string serialPort, int baudRate, string chipType, string flashM
Console.WriteLine($"MAC address: {mac}");

// execute flash_id command and parse the result
if (!RunEspTool("flash_id", out messages))
if (!RunEspTool("flash_id", true, null, out messages))
{
Console.WriteLine(messages);
return null;
Expand All @@ -220,33 +225,57 @@ internal EspTool(string serialPort, int baudRate, string chipType, string flashM
Console.WriteLine($"Flash information: manufacturer 0x{manufacturer} device 0x{device} size {size}");

// collect and return all information
// convert the flash size from megabytes into bytes
_flashSize = int.Parse(size.Remove(size.Length - 2)) * 0x100000;
// convert the flash size into bytes
string unit = size.Substring(size.Length - 2).ToUpperInvariant();
_flashSize = int.Parse(size.Remove(size.Length - 2)) * (unit == "MB" ? 0x100000 : unit == "KB" ? 0x400 : 1);
return new Info(
new Version(version), // esptool.py version
name, // ESP32 name
features, // ESP32 chip features
long.Parse(id.Substring(2), NumberStyles.HexNumber), // ESP32 Chip-ID
PhysicalAddress.Parse(mac.Replace(':', '-').ToUpperInvariant()), // ESP32 MAC address
name, // ESP32/ESP8266 name
features, // ESP32/ESP8266 chip features
long.Parse(id.Substring(2), NumberStyles.HexNumber), // ESP32/ESP8266 Chip-ID
PhysicalAddress.Parse(mac.Replace(':', '-').ToUpperInvariant()), // ESP32/ESP8266 MAC address
byte.Parse(manufacturer, NumberStyles.AllowHexSpecifier), // flash manufacturer ID
short.Parse(device, NumberStyles.HexNumber), // flash device ID
_flashSize); // flash size in bytes (converted from megabytes)
}

/// <summary>
/// Erase the entire flash of the ESP32 chip
/// Backup the entire flash into a bin file
/// </summary>
/// <param name="backupFilename">Backup file incl. full path</param>
/// <param name="flashSize">Flash size in bytes</param>
/// <returns>true if successful</returns>
internal bool BackupFlash(string backupFilename, int flashSize)
{
// execute read_flash command and parse the result; progress message can be found be searching for backspaces (ASCII code 8)
if (!RunEspTool($"read_flash 0 0x{flashSize:X} \"{backupFilename}\"", false, (char)8, out string messages))
{
Console.WriteLine(messages);
return false;
}
Match match = Regex.Match(messages, "(?<message>Read .*)(.*?\n)*");
if (!match.Success)
{
Console.WriteLine(messages);
return false;
}
Console.WriteLine(match.Groups["message"].ToString().Trim());
return true;
}

/// <summary>
/// Erase the entire flash of the ESP32/ESP8266 chip
/// </summary>
/// <returns>true if successful</returns>
internal bool EraseFlash()
{
string messages;
// execute erase_flash command and parse the result
if (!RunEspTool("erase_flash", out messages))
if (!RunEspTool("erase_flash", false, null, out string messages))
{
Console.WriteLine(messages);
return false;
}
Match match = Regex.Match(messages, "(\\(this may take a while\\))(.*?\n)(?<message>.*)(.*?\n)*");
Match match = Regex.Match(messages, "(?<message>Chip erase completed successfully.*)(.*?\n)*");
if (!match.Success)
{
Console.WriteLine(messages);
Expand All @@ -256,28 +285,39 @@ internal bool EraseFlash()
return true;
}

/// <summary>
/// Write to the flash
/// </summary>
/// <param name="partsToWrite">dictionary which keys are the start addresses and the values are the complete filenames (the bin files)</param>
/// <returns>true if successful</returns>
internal bool WriteFlash(Dictionary<int, string> partsToWrite)
{
// put the parts to flash together and prepare the regex for parsing the output
StringBuilder partsArguments = new StringBuilder();
StringBuilder regexPattern = new StringBuilder("(?<params>Flash params.*)(.*?[\r\n]*)*");
StringBuilder regexPattern = new StringBuilder();
int counter = 1;
List<string> regexGroupNames = new List<string>();
regexGroupNames.Add("params");
foreach (KeyValuePair<int, string> part in partsToWrite)
{
// start address followed by filename
partsArguments.Append($"0x{part.Key:X} {part.Value} ");
partsArguments.Append($"0x{part.Key:X} \"{part.Value}\" ");
// test for message in output
regexPattern.Append($"(?<wrote{counter}>Wrote.*[\r\n]*Hash of data verified.)(.*?[\r\n]*)*");
regexGroupNames.Add($"wrote{counter}");
counter++;
}
string messages;
// if flash size was detected already use it for the --flash_size parameter; otherwise use the default "detect"
string flashSize = _flashSize != -1 ? $"{_flashSize / 0x100000}MB" : "detect";
// execute write_flash command and parse the result
if (!RunEspTool($"write_flash --flash_mode {_flashMode} --flash_freq {_flashFrequency / 1000000}m --flash_size {flashSize} {partsArguments.ToString().Trim()}", out messages))
string flashSize = "detect";
if (_flashSize >= 0x100000)
{
flashSize = $"{_flashSize / 0x100000}MB";
}
else if (_flashSize > 0)
{
flashSize = $"{_flashSize / 0x400}KB";
}
// execute write_flash command and parse the result; progress message can be found be searching for linefeed
if (!RunEspTool($"write_flash --flash_mode {_flashMode} --flash_freq {_flashFrequency / 1000000}m --flash_size {flashSize} {partsArguments.ToString().Trim()}", false, '\r', out string messages))
{
Console.WriteLine(messages);
return false;
Expand All @@ -299,31 +339,119 @@ internal bool WriteFlash(Dictionary<int, string> partsToWrite)
/// Run the esptool one time
/// </summary>
/// <param name="commandWithArguments">the esptool command (e.g. write_flash) incl. all arguments (if needed)</param>
/// <param name="noStub">if true --no-stub will be added; the chip_id, read_mac and flash_id commands can be quicker executes without uploading the stub program to the chip</param>
/// <param name="tryToShowProgress">Tries to show progress every second if possible</param>
/// <param name="messages">StandardOutput and StandardError messages that the esptool prints out</param>
/// <returns>true if the esptool exit code was 0; false otherwise</returns>
private bool RunEspTool(string commandWithArguments, out string messages)
private bool RunEspTool(string commandWithArguments, bool noStub, char? progressTestChar, out string messages)
{
// create the process start info
// if we can directly talt to the ROM bootloader without a stub program use the --no-stub option
// --nostub requires to not change the baudrate (ROM doesn't support changing baud rate. Keeping initial baud rate 115200)
string noStubParameter = null;
string baudRateParameter = null;
if (noStub)
{
// using no stub and can't change the baud rate
noStubParameter = "--no-stub";
}
else
{
// using the stub that supports changing the baudrate
baudRateParameter = $"--baud {_baudRate}";
}

// prepare the process start of the esptool
Process espTool = new Process();
espTool.StartInfo = new ProcessStartInfo(@"esptool\esptool.exe", $"--port {_serialPort} --baud {_baudRate} --chip {_chipType.ToLowerInvariant()} --after no_reset {commandWithArguments}");
espTool.StartInfo = new ProcessStartInfo(@"esptool\esptool.exe", $"--port {_serialPort} {baudRateParameter} --chip {_chipType.ToLowerInvariant()} {noStubParameter} --after no_reset {commandWithArguments}");
espTool.StartInfo.UseShellExecute = false;
espTool.StartInfo.RedirectStandardError = true;
espTool.StartInfo.RedirectStandardOutput = true;

// start esptool and wait for exit
if (espTool.Start())
{
espTool.WaitForExit();
// if no progress output needed wait unlimited time until esptool exit
if (!progressTestChar.HasValue)
{
espTool.WaitForExit();
}
}
else
{
Console.WriteLine("Error starting esptool!");
}

// collect all messages
messages = string.Concat(espTool.StandardOutput.ReadToEnd(), Environment.NewLine, espTool.StandardError.ReadToEnd());
StringBuilder messageBuilder = new StringBuilder();
// showing progress is a little bit tricky
if (progressTestChar.HasValue)
{
// loop until esptool exit
while (!espTool.HasExited)
{
// loop until there is no next char to read from standard output
while (true)
{
int next = espTool.StandardOutput.Read();
if (next != -1)
{
// append the char to the message buffer
char nextChar = (char)next;
messageBuilder.Append((char)next);
// try to find a progress message
string progress = FindProgress(messageBuilder, progressTestChar.Value);
if (progress != null)
{
// print progress and set the cursor to the beginning of the line (\r)
Console.Write(progress);
Console.Write("\r");
}
}
else
{
break;
}
}
}
// collect the last messages
messageBuilder.AppendLine(espTool.StandardOutput.ReadToEnd());
messageBuilder.Append(espTool.StandardError.ReadToEnd());
}
else
{
// collect all messages
messageBuilder.AppendLine(espTool.StandardOutput.ReadToEnd());
messageBuilder.Append(espTool.StandardError.ReadToEnd());
}
// true if exit code was 0 (success)
messages = messageBuilder.ToString();
return espTool.ExitCode == 0;
}

/// <summary>
/// Try to find a progress message in the esptool output
/// </summary>
/// <param name="messageBuilder">esptool output</param>
/// <param name="progressTestChar">search char for the progress message delimiter (backspace or linefeed)</param>
/// <returns></returns>
private string FindProgress(StringBuilder messageBuilder, char progressTestChar)
{
// search for the given char (backspace or linefeed)
// only if we have 100 chars at minimum and only if the last char is the test char
if (messageBuilder.Length > 100 && messageBuilder[messageBuilder.Length - 1] == progressTestChar && messageBuilder[messageBuilder.Length - 2] != progressTestChar)
{
// trim the test char and convert \r\n into \r
string progress = messageBuilder.ToString().Trim(progressTestChar).Replace("\r\n", "\r");
// another test char in the message?
int delimiter = progress.LastIndexOf(progressTestChar);
if (delimiter > 0)
{
// then we found a progress message; pad the message to 110 chars because no message is longer than 110 chars
return progress.Substring(delimiter + 1).PadRight(110);
}
}
// no progress message found
return null;
}
}
}
Loading

0 comments on commit f2e0129

Please sign in to comment.