Files

1285 lines
58 KiB
C++
Raw Permalink Normal View History

/************************************************************************
* Copyright (c) 2009-2011, Microchip Technology Inc.
*
* Microchip licenses this software to you solely for use with Microchip
* products. The software is owned by Microchip and its licensors, and
* is protected under applicable copyright laws. All rights reserved.
*
* SOFTWARE IS PROVIDED "AS IS." MICROCHIP EXPRESSLY DISCLAIMS ANY
* WARRANTY OF ANY KIND, WHETHER EXPRESS OR IMPLIED, INCLUDING BUT
* NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. IN NO EVENT SHALL
* MICROCHIP BE LIABLE FOR ANY INCIDENTAL, SPECIAL, INDIRECT OR
* CONSEQUENTIAL DAMAGES, LOST PROFITS OR LOST DATA, HARM TO YOUR
* EQUIPMENT, COST OF PROCUREMENT OF SUBSTITUTE GOODS, TECHNOLOGY
* OR SERVICES, ANY CLAIMS BY THIRD PARTIES (INCLUDING BUT NOT LIMITED
* TO ANY DEFENSE THEREOF), ANY CLAIMS FOR INDEMNITY OR CONTRIBUTION,
* OR OTHER SIMILAR COSTS.
*
* To the fullest extent allowed by law, Microchip and its licensors
* liability shall not exceed the amount of fees, if any, that you
* have paid directly to Microchip to use this software.
*
* MICROCHIP PROVIDES THIS SOFTWARE CONDITIONALLY UPON YOUR ACCEPTANCE
* OF THESE TERMS.
*
* Author Date Ver Comment
*************************************************************************
* E. Schlunder 2009/04/14 0.01 Initial code ported from VB app.
* T. Lawrence 2011/01/14 2.9 Initial implementation of USB version of this
* bootloader application.
* F. Schlunder 2011/07/06 2.9a Small update to support importing of hex files
* with "non-monotonic" line address ordering.
* F. Schlunder 2015/07/28 2.13 Added ability to override default VID/PID by specifing
* new values when launching the .exe.
* Example Usage:
* HIDBootloader.exe 0x04D8 0x1234
* Where "0x04D8" is the target VID (of the bootloader firmware)
* Where "0x1234" is the target PID (of the bootloader firmware)
* Also added ability to specify optional third parameter (a .hex file)
* to be programmed immediately (ex: for non-GUI/fully automated programming
* operation, where the bootloader immediately programs the device and then exits).
* Example Usage:
* HIDBootloader.exe 0x04D8 0x1234 TheNewApplicationImage.hex
************************************************************************/
#include <QTextStream>
#include <QByteArray>
#include <QList>
#include <QTime>
#include <QtWidgets/QFileDialog>
#include <QtWidgets/QMessageBox>
#include <QSettings>
#include <QtWidgets/QDesktopWidget>
#include <QtConcurrent/QtConcurrentRun>
#include "MainWindow.h"
#include "ui_MainWindow.h"
#include "Settings.h"
#include "../version.h"
// Device Vendor and Product IDs. These values should match the values used in the USB device firmware (in the device descriptor).
// These are the default values that this PC application will serach for when trying to find/connect to the device. If this application
// is called from the command line with extra parameters (specifying new VID and PID values), then those values will be used instead
// of these default values.
#define DEFAULT_VID 0x04D8
#define DEFAULT_PID 0x003C
// the command to make the device jump into bootloader mode
#define COMMAND_JUMP_BOOTLOADER 0xBB
struct USBDevIds
{
unsigned short vendor_id;
unsigned short product_id;
} NormalModeDevIds[2] =
{
{0x1209, 0x0600}, // from pid.codes
{0x04D8, 0xECEE} // from microchip sublicense
};
// If the user ignores the popup to reset a device in normal mode into bootloader mode
// the path of the device that triggered the popup will be added to this list and will
// not prompt the user again unless it gets disconnected from the computer.
QList<QString> IgnoredHIDDevsList;
//Surely the micro doesn't have a programmable memory region greater than 268 Megabytes...
//Value used for error checking device reponse values.
#define MAXIMUM_PROGRAMMABLE_MEMORY_SEGMENT_SIZE 0x0FFFFFFF
bool deviceFirmwareIsAtLeast101 = false;
Comm::ExtendedQueryInfo extendedBootInfo;
volatile bool consoleInvokedEraseProgramVerifyComplete = false;
volatile bool consoleEraseProgramVerifiedOkay = false;
uint16_t DeviceVID;
uint16_t DevicePID;
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindowClass)
{
hexOpen = false;
timer = new QTimer();
ui->setupUi(this);
setWindowTitle(QCoreApplication::applicationName() + QString(" v") + VERSION);
2019-08-19 16:56:38 -04:00
// by default hide the advanced controls
// (qtimer with 0 delay is a dumb way to make sure this is called after the window finishes updating geometry)
QTimer::singleShot(0, this, [this]() {
on_checkBoxAdvancedMode_stateChanged(0);
});
2019-08-17 12:33:27 -04:00
QSettings settings;
settings.beginGroup("WriteOptions");
writeFlash = settings.value("writeFlash", true).toBool();
writeEeprom = settings.value("writeEeprom", false).toBool();
settings.endGroup();
comm = new Comm();
deviceData = new DeviceData();
hexData = new DeviceData();
device = new Device(deviceData);
qRegisterMetaType<Comm::ErrorCode>("Comm::ErrorCode");
connect(ui->btnFlashFirmware, &QPushButton::clicked, this, [=](){
future = QtConcurrent::run([this](){
Comm::ErrorCode subResult;
//Update the progress bar so the user knows things are happening.
emit SetProgressBar(3);
//First erase the entire device.
emit IoWithDeviceStateChanged("(1/3) Erasing device...");
subResult = EraseDevice();
if (subResult != Comm::Success)
{
emit IoWithDeviceStateChanged("Erasing device failed!");
QMessageBox::critical(this, "Device Flashing Failed",
"An error ocurred while erasing the device. Please disconnect "
"and reconnect the device and try again.");
return subResult;
}
//then write the new firmware to the device
emit IoWithDeviceStateChanged("(2/3) Writing device...");
subResult = WriteDevice();
if (subResult != Comm::Success)
{
emit IoWithDeviceStateChanged("Writing device failed!");
QMessageBox::critical(this, "Device Flashing Failed",
"An error ocurred while writing the device. Please disconnect "
"and reconnect the device and try again.");
return subResult;
}
//finally verify the device's memory and write the signature word
emit IoWithDeviceStateChanged("(3/3) Verifying device...");
subResult = VerifyDevice();
emit SetProgressBar(100);
if (subResult != Comm::Success)
{
emit IoWithDeviceStateChanged("Verifying device failed!");
QMessageBox::critical(this, "Device Flashing Failed",
"An error ocurred while verifying the device. Please disconnect "
"and reconnect the device and try again.");
return subResult;
}
emit AppendString("Firmware was sucessfully flashed");
emit IoWithDeviceStateChanged("Ready.");
emit UserMustResetPrompt();
return Comm::Success;
});
ui->plainTextEdit->clear();
ui->plainTextEdit->appendPlainText("Starting Erase/Program/Verify Sequence.");
ui->plainTextEdit->appendPlainText("Do not unplug device or disconnect power until the operation is fully complete.");
ui->plainTextEdit->appendPlainText(" ");
});
connect(timer, SIGNAL(timeout()), this, SLOT(Connection()));
connect(this, SIGNAL(IoWithDeviceCompleted(QString,Comm::ErrorCode,double)), this, SLOT(IoWithDeviceComplete(QString,Comm::ErrorCode,double)));
connect(this, SIGNAL(IoWithDeviceStarted(QString)), this, SLOT(IoWithDeviceStart(QString)));
connect(this, SIGNAL(AppendString(QString)), this, SLOT(AppendStringToTextbox(QString)));
connect(this, SIGNAL(SetProgressBar(int)), this, SLOT(UpdateProgressBar(int)));
connect(comm, SIGNAL(SetProgressBar(int)), this, SLOT(UpdateProgressBar(int)));
connect(this, &MainWindow::IoWithDeviceStateChanged, this, &MainWindow::IoWithDeviceStateChange);
connect(this, &MainWindow::UserMustResetPrompt, this, &MainWindow::PromptUserToReset);
//Assume initally that the USB device that we are trying to find is using the default VID/PID.
DeviceVID = DEFAULT_VID;
DevicePID = DEFAULT_PID;
//Check if the caller specified alternate VID and PID values by specifying them at the command line when they called this application
//ex: HIDBootloader.exe 0x04D8 0x1234
//Where the first number string is the VID and the second number provided is the PID, of the USB device bootloader
//firmware (the "application" or non-bootloader firmware VID and PID don't matter and aren't relevant to this program,
//only the bootloader VID/PID).
//If the caller provided VID/PID values, use those values instead of the DEFAULT_VID and DEFAULT_PID when searching/connecting
//to the USB device.
//Fetch an array of strings of the input parmaters that the caller used when invoking the application.
QStringList args = QCoreApplication::arguments();
if(args.size() >= 2)
{
//Check if the user passed in a VID string when invoking the program.
QString firstParameter = args.value(1);
if((firstParameter.size() == 4) || (firstParameter.size() == 6)) //could be 4 characters long if no leading "0x" was provided, or 6 chars if the full thing was provided, ex: "0x04D8"
{
bool status;
//Convert the hex ASCII string into a proper ulong type variable.
ulong HexVID = firstParameter.toULong(&status, 16);
if(status == true)
{
//Make sure the ulong converted from the text string was a 16-bit hex value.
if(HexVID <= 0x0000FFFF)
{
//Looks okay. Use the value as the real value to search for when looking for the USB device.
DeviceVID = HexVID;
}
}
}
//Check if the user passed in a PID string when invoking the program.
if(args.size() >= 3)
{
QString secondParameter = args.value(2);
if((secondParameter.size() == 4) || (secondParameter.size() == 6))
{
bool status;
ulong HexPID = secondParameter.toULong(&status, 16);
if(status == true)
{
//Make sure the ulong converted from the text string was a 16-bit hex value.
if(HexPID <= 0x0000FFFF)
{
//Looks okay. Use the value as the real value to search for when looking for the USB device.
DevicePID = HexPID;
}
}
}
}
}
//Make initial check to see if the USB device is attached
comm->PollUSB(DeviceVID, DevicePID);
if(comm->isConnected())
{
qWarning("Attempting to open device...");
comm->open(DeviceVID, DevicePID);
ui->plainTextEdit->setPlainText("Device Attached.");
ui->plainTextEdit->appendPlainText("Connecting...");
GetQuery();
}
else
{
ui->plainTextEdit->appendPlainText("Device not detected. Verify device is attached and in firmware update mode.");
hexOpen = false;
setBootloadEnabled(false);
emit SetProgressBar(0);
}
timer->start(1000); //Check for future USB connection status changes every 1000 milliseconds.
//Check if the user ran the .exe with extensions pointing staight to the hex file of interest to be programmed.
//If the device is connected, and the extra info was provided, jump straight to erase/program/verify operation
//and then exit the program. This is useful if you want to use the bootloader in a more production or automated
//environment where you don't necessarily want to have to click a button on the GUI.
if(args.size() >= 4)
{
//Copy the .hex file name/path string from the string array into a separate string.
QString argument = args.value(3);
if(argument.endsWith(".hex", Qt::CaseInsensitive))
{
//Make sure the device was already connected in bootloader mode and ready.
if(comm->isConnected())
{
//Load up the .hex file from the hard drive
LoadFile(argument);
printf("Found device and hex file. Programming device...");
//Start the thread that does the erase/program/verify sequence.
future = QtConcurrent::run(this, &MainWindow::WriteDevice);
//Wait for the thread to finish up.
while((future.isFinished() == false) && (consoleInvokedEraseProgramVerifyComplete == false))
{
QThread::msleep(5);
}
//Check the status and exit the application.
printf("\r\nFinished erase/program/verify sequence.");
if(consoleEraseProgramVerifiedOkay == true)
{
//Successfully erase/program/verified via console invocation.
printf(" Operation successful.");
//Now command the USB device to reset itself. This would normally switch it out
//of USB HID bootloader mode, into application run mode (although the exact
//behavior will depend upon how the bootloader firmware is implemented and what is uses
//as an entry method into the bootloader mode).
comm->Reset();
//Exit the application now and return '0', which indicates success to the caller.
exit(0);
}
else
{
//Failure during verify. Return something other than '0' to let the caller
//know something unexpected happened.
printf(" Operation FAILED!");
exit(-366);
}
}
else
{
//The device isn't in bootloader mode or otherwise couldn't be connected with.
//In this scenario, we should promptly exit with failure status, so the automated caller
//knows something is wrong promptly (rather than launching the full GUI and waiting indefinitely
//for a human to do something).
printf("\r\nDevice was not found. Exiting application...");
exit(-367);
}
}
}
}
MainWindow::~MainWindow()
{
QSettings settings;
settings.beginGroup("WriteOptions");
settings.setValue("writeFlash", writeFlash);
settings.setValue("writeEeprom", writeEeprom);
settings.endGroup();
comm->close();
setBootloadEnabled(false);
delete timer;
delete ui;
delete comm;
delete deviceData;
delete hexData;
delete device;
}
void MainWindow::Connection(void)
{
bool currStatus = comm->isConnected();
comm->PollUSB(DeviceVID, DevicePID);
if(currStatus != comm->isConnected())
{
if(comm->isConnected())
{
qWarning("Attempting to open device...");
comm->open(DeviceVID, DevicePID);
ui->plainTextEdit->setPlainText("Device Attached.");
ui->plainTextEdit->appendPlainText("Connecting...");
GetQuery();
}
else
{
qWarning("Closing device.");
comm->close();
ui->plainTextEdit->setPlainText("Device Detached.");
ui->lblDeviceConnected->setText("Not Connected");
ui->lblDeviceConnected->setStyleSheet("QLabel { background-color : red; }");
ui->lblDeviceStatusMsg->setText("Device not detected. Please connect the dancepad interface's USB cable to this computer.");
hexOpen = false;
setBootloadEnabled(false);
emit SetProgressBar(0);
}
}
// check if a device is found in normal non-bootloader mode
hid_device_info *hidDevs = hid_enumerate(0, 0);
hid_device_info *d = hidDevs;
// a new ignored list will be created & only currently ignored devices that are still connected will be saved
QList<QString> filteredIgnoredDevsList;
bool ignoreRemaining = false;
while (d)
{
// check if the device is ignored & add to new list if it is
QString qstrPath = QString::fromUtf8(d->path);
if (IgnoredHIDDevsList.contains(qstrPath))
{
filteredIgnoredDevsList.append(qstrPath);
d = d->next;
continue;
}
// if user ignored a previous device do not continue checking for matching devices
if (ignoreRemaining)
{
d = d->next;
continue;
}
// check if the device matches any of our PID/VID combinations
for (unsigned int i=0;i<sizeof(NormalModeDevIds)/sizeof(USBDevIds);i++)
{
USBDevIds ids = NormalModeDevIds[i];
if (d->vendor_id == ids.vendor_id &&
d->product_id == ids.product_id)
{
// a device was found, prompt user that it will need to be reset into bootloader mode
QMessageBox msgbox(QMessageBox::Information,
"Device detected in non-FW update mode",
"A device was detected in normal operating mode. Press OK to reset the device into firmware update mode and proceed.",
QMessageBox::Ok | QMessageBox::Ignore,
this);
msgbox.setWindowModality(Qt::ApplicationModal);
msgbox.setWindowFlags(msgbox.windowFlags() & ~Qt::WindowContextHelpButtonHint);
// if user chose to ignore, add device to ignored list, exit the loop & cleanup HID resources
if (msgbox.exec() != QMessageBox::Ok)
{
ignoreRemaining = true;
filteredIgnoredDevsList.append(qstrPath);
break;
}
// open the HID device and send the command to it
hid_device *dev = hid_open_path(d->path);
if (dev == NULL)
{
QMessageBox::critical(this, "Error", "Failed to open the HID device");
break;
}
unsigned char hidOutData[2] = {0, COMMAND_JUMP_BOOTLOADER};
int bytesWritten = hid_write(dev, &hidOutData[0], sizeof(hidOutData));
if (bytesWritten == -1)
{
QString errMsg = QString("Failed to send mode switch command to the device:\n\n%1").arg(hid_error(dev));
QMessageBox::critical(this, "Error", errMsg);
}
hid_close(dev);
// do not continue iterating over the list of enumerated HID devices
ignoreRemaining = true;
break;
}
}
d = d->next;
}
// copy the latest ignored devices array to the global list
IgnoredHIDDevsList = QList<QString>(filteredIgnoredDevsList);
hid_free_enumeration(hidDevs);
}
void MainWindow::setBootloadEnabled(bool enable)
{
ui->groupFlashFirmware->setEnabled(enable && hexOpen);
ui->groupFirmwareFile->setEnabled(enable);
ui->btnResetDevice->setEnabled(enable);
ui->progressBar->reset();
if (!enable)
{
ui->lblFlashStatus->setText("Not ready.");
ui->lblDeviceBootloaderVer->setText("");
ui->lblDeviceAppFWVer->setText("");
}
}
void MainWindow::setBootloadBusy(bool busy)
{
if(busy)
{
QApplication::setOverrideCursor(Qt::BusyCursor);
timer->stop();
}
else
{
QApplication::restoreOverrideCursor();
timer->start(1000);
}
ui->btnFlashFirmware->setEnabled(!busy && hexOpen);
ui->groupFirmwareFile->setEnabled(!busy);
ui->btnResetDevice->setEnabled(!busy);
}
void MainWindow::IoWithDeviceStart(QString msg)
{
ui->plainTextEdit->appendPlainText(msg);
setBootloadBusy(true);
}
//Useful for adding lines of text to the main window from other threads.
void MainWindow::AppendStringToTextbox(QString msg)
{
ui->plainTextEdit->appendPlainText(msg);
}
void MainWindow::UpdateProgressBar(int newValue)
{
ui->progressBar->setValue(newValue);
}
void MainWindow::IoWithDeviceComplete(QString msg, Comm::ErrorCode result, double time)
{
QTextStream ss(&msg);
switch(result)
{
case Comm::Success:
ss << " Complete (" << time << "s)\n";
setBootloadBusy(false);
break;
case Comm::NotConnected:
ss << " Failed. Device not connected.\n";
setBootloadBusy(false);
break;
case Comm::Fail:
ss << " Failed.\n";
setBootloadBusy(false);
break;
case Comm::IncorrectCommand:
ss << " Failed. Unable to communicate with device.\n";
setBootloadBusy(false);
break;
case Comm::Timeout:
ss << " Timed out waiting for device (" << time << "s)\n";
setBootloadBusy(false);
break;
default:
break;
}
ui->plainTextEdit->appendPlainText(msg);
if (result != Comm::Success)
{
ui->lblFlashStatus->setText(*ss.string());
QMessageBox::critical(this, "Error", *ss.string());
}
}
void MainWindow::IoWithDeviceStateChange(QString msg)
{
ui->lblFlashStatus->setText(msg);
}
void MainWindow::PromptUserToReset()
{
QMessageBox::StandardButton resButton;
resButton = QMessageBox::information(this, "Firmware was sucessfully flashed",
"Reset device now?"
"(Note: Device must be reset or physically disconnected and "
"reconnected before it can be used.)",
QMessageBox::Yes | QMessageBox::No,
QMessageBox::Yes);
//reset device if user chose to
if (resButton == QMessageBox::Yes)
on_btnResetDevice_clicked();
}
//Routine that verifies the contents of the non-voltaile memory regions in the device, after an erase/programming cycle.
//This function requests the memory contents of the device, then compares it against the parsed .hex file data to make sure
//The locations that got programmed properly match.
Comm::ErrorCode MainWindow::VerifyDevice()
{
Comm::ErrorCode result;
DeviceData::MemoryRange deviceRange, hexRange;
QTime elapsed;
unsigned int i, j;
bool failureDetected = false;
unsigned char flashData[MAX_ERASE_BLOCK_SIZE];
unsigned char hexEraseBlockData[MAX_ERASE_BLOCK_SIZE];
uint32_t startOfEraseBlock;
uint32_t errorAddress = 0;
uint16_t expectedResult = 0;
uint16_t actualResult = 0;
//Initialize an erase block sized buffer with 0xFF.
//Used later for post SIGN_FLASH verify operation.
memset(&hexEraseBlockData[0], 0xFF, MAX_ERASE_BLOCK_SIZE);
emit IoWithDeviceStarted("Verifying Device...");
elapsed.start();
foreach(deviceRange, deviceData->ranges)
{
if(deviceRange.type == PROGRAM_MEMORY)
{
result = comm->GetData(deviceRange.start,
device->bytesPerPacket,
device->bytesPerAddressFLASH,
device->bytesPerWordFLASH,
deviceRange.end,
deviceRange.pDataBuffer);
if(result != Comm::Success)
{
failureDetected = true;
qWarning("Error reading device.");
}
//Search through all of the programmable memory regions from the parsed .hex file data.
//For each of the programmable memory regions found, if the region also overlaps a region
//that was included in the device programmed area (which just got read back with GetData()),
//then verify both the parsed hex contents and read back data match.
foreach(hexRange, hexData->ranges)
{
if(deviceRange.start == hexRange.start)
{
//For this entire programmable memory address range, check to see if the data read from the device exactly
//matches what was in the hex file.
for(i = deviceRange.start; i < deviceRange.end; i++)
{
//For each byte of each device address (1 on PIC18, 2 on PIC24, since flash memory is 16-bit WORD array)
for(j = 0; j < device->bytesPerAddressFLASH; j++)
{
//Check if the device response data matches the data we parsed from the original input .hex file.
if(deviceRange.pDataBuffer[((i - deviceRange.start) * device->bytesPerAddressFLASH)+j] != hexRange.pDataBuffer[((i - deviceRange.start) * device->bytesPerAddressFLASH)+j])
{
//A mismatch was detected.
//Check if this is a PIC24 device and we are looking at the "phantom byte"
//(upper byte [j = 1] of odd address [i%2 == 1] 16-bit flash words). If the hex data doesn't match
//the device (which should be = 0x00 for these locations), this isn't a real verify
//failure, since value is a don't care anyway. This could occur if the hex file imported
//doesn't contain all locations, and we "filled" the region with pure 0xFFFFFFFF, instead of 0x00FFFFFF
//when parsing the hex file.
if((device->family == Device::PIC24) && ((i % 2) == 1) && (j == 1))
{
//Not a real verify failure, phantom byte is unimplemented and is a don't care.
}
else
{
//If the data wasn't a match, and this wasn't a PIC24 phantom byte, then if we get
//here this means we found a true verify failure.
failureDetected = true;
if(device->family == Device::PIC24)
{
qWarning("Device: 0x%x Hex: 0x%x", *(uint16_t*)&deviceRange.pDataBuffer[((i - deviceRange.start) * device->bytesPerAddressFLASH)+j], *(uint16_t*)&hexRange.pDataBuffer[((i - deviceRange.start) * device->bytesPerAddressFLASH)+j]);
}
else
{
qWarning("Device: 0x%x Hex: 0x%x", deviceRange.pDataBuffer[((i - deviceRange.start) * device->bytesPerAddressFLASH)+j], hexRange.pDataBuffer[((i - deviceRange.start) * device->bytesPerAddressFLASH)+j]);
}
qWarning("Failed verify at address 0x%x", i);
emit IoWithDeviceCompleted("Verify", Comm::Fail, ((double)elapsed.elapsed()) / 1000);
return Comm::Fail;
}
}//if(deviceRange.pDataBuffer[((i - deviceRange.start) * device->bytesPerAddressFLASH)+j] != hexRange.pDataBuffer[((i - deviceRange.start) * device->bytesPerAddressFLASH)+j])
}//for(j = 0; j < device->bytesPerAddressFLASH; j++)
}//for(i = deviceRange.start; i < deviceRange.end; i++)
}//if(deviceRange.start == hexRange.start)
}//foreach(hexRange, hexData->ranges)
//emit IoWithDeviceCompleted("Verify", Comm::Success, ((double)elapsed.elapsed()) / 1000);
}//if(writeFlash && (deviceRange.type == PROGRAM_MEMORY))
else if(writeEeprom && (deviceRange.type == EEPROM_MEMORY))
{
result = comm->GetData(deviceRange.start,
device->bytesPerPacket,
device->bytesPerAddressEEPROM,
device->bytesPerWordEEPROM,
deviceRange.end,
deviceRange.pDataBuffer);
if(result != Comm::Success)
{
failureDetected = true;
qWarning("Error reading device.");
//emit IoWithDeviceCompleted("Verifying Device's EEPROM Memory", result, ((double)elapsed.elapsed()) / 1000);
}
//Search through all of the programmable memory regions from the parsed .hex file data.
//For each of the programmable memory regions found, if the region also overlaps a region
//that was included in the device programmed area (which just got read back with GetData()),
//then verify both the parsed hex contents and read back data match.
foreach(hexRange, hexData->ranges)
{
if(deviceRange.start == hexRange.start)
{
//For this entire programmable memory address range, check to see if the data read from the device exactly
//matches what was in the hex file.
for(i = deviceRange.start; i < deviceRange.end; i++)
{
//For each byte of each device address (only 1 for EEPROM byte arrays, presumably 2 for EEPROM WORD arrays)
for(j = 0; j < device->bytesPerAddressEEPROM; j++)
{
//Check if the device response data matches the data we parsed from the original input .hex file.
if(deviceRange.pDataBuffer[((i - deviceRange.start) * device->bytesPerAddressEEPROM)+j] != hexRange.pDataBuffer[((i - deviceRange.start) * device->bytesPerAddressEEPROM)+j])
{
//A mismatch was detected.
failureDetected = true;
qWarning("Device: 0x%x Hex: 0x%x", deviceRange.pDataBuffer[((i - deviceRange.start) * device->bytesPerAddressFLASH)+j], hexRange.pDataBuffer[((i - deviceRange.start) * device->bytesPerAddressFLASH)+j]);
qWarning("Failed verify at address 0x%x", i);
emit IoWithDeviceCompleted("Verify EEPROM Memory", Comm::Fail, ((double)elapsed.elapsed()) / 1000);
return Comm::Fail;
}
}
}
}
}//foreach(hexRange, hexData->ranges)
//emit IoWithDeviceCompleted("Verifying", Comm::Success, ((double)elapsed.elapsed()) / 1000);
}//else if(writeEeprom && (deviceRange.type == EEPROM_MEMORY))
else
{
continue;
}
}//foreach(deviceRange, deviceData->ranges)
if(failureDetected == false)
{
//Successfully verified all regions without error.
//If this is a v1.01 or later device, we now need to issue the SIGN_FLASH
//command, and then re-verify the first erase page worth of flash memory
//(but with the exclusion of the signature WORD address from the verify,
//since the bootloader firmware will have changed it to the new/magic
//value (probably 0x600D, or "good" in leet speak).
if(deviceFirmwareIsAtLeast101 == true)
{
comm->SignFlash();
qDebug("Expected Signature Address: 0x%x", extendedBootInfo.PIC18.signatureAddress);
qDebug("Expected Signature Value: 0x%x", extendedBootInfo.PIC18.signatureValue);
//Now re-verify the first erase page of flash memory.
if(device->family == Device::PIC18)
{
startOfEraseBlock = extendedBootInfo.PIC18.signatureAddress - (extendedBootInfo.PIC18.signatureAddress % extendedBootInfo.PIC18.erasePageSize);
result = comm->GetData(startOfEraseBlock,
device->bytesPerPacket,
device->bytesPerAddressFLASH,
device->bytesPerWordFLASH,
(startOfEraseBlock + extendedBootInfo.PIC18.erasePageSize),
&flashData[0]);
if(result != Comm::Success)
{
failureDetected = true;
qWarning("Error reading, post signing, flash data block.");
}
//Search through all of the programmable memory regions from the parsed .hex file data.
//For each of the programmable memory regions found, if the region also overlaps a region
//that is part of the erase block, copy out bytes into the hexEraseBlockData[] buffer,
//for re-verification.
foreach(hexRange, hexData->ranges)
{
//Check if any portion of the range is within the erase block of interest in the device.
if((hexRange.start <= startOfEraseBlock) && (hexRange.end > startOfEraseBlock))
{
unsigned int rangeSize = hexRange.end - hexRange.start;
unsigned int address = hexRange.start;
unsigned int k = 0;
//Check every byte in the hex file range, to see if it is inside the erase block of interest
for(i = 0; i < rangeSize; i++)
{
//Check if the current byte we are looking at is inside the erase block of interst
if(((address+i) >= startOfEraseBlock) && ((address+i) < (startOfEraseBlock + extendedBootInfo.PIC18.erasePageSize)))
{
//The byte is in the erase block of interst. Copy it out into a new buffer.
hexEraseBlockData[k] = *(hexRange.pDataBuffer + i);
//Check if this is a signature byte. If so, replace the value in the buffer
//with the post-signing expected signature value, since this is now the expected
//value from the device, rather than the value from the hex file...
if((address+i) == extendedBootInfo.PIC18.signatureAddress)
{
hexEraseBlockData[k] = (unsigned char)extendedBootInfo.PIC18.signatureValue; //Write LSB of signature into buffer
}
if((address+i) == (extendedBootInfo.PIC18.signatureAddress + 1))
{
hexEraseBlockData[k] = (unsigned char)(extendedBootInfo.PIC18.signatureValue >> 8); //Write MSB into buffer
}
k++;
}
if((k >= extendedBootInfo.PIC18.erasePageSize) || (k >= sizeof(hexEraseBlockData)))
break;
}//for(i = 0; i < rangeSize; i++)
}
}//foreach(hexRange, hexData->ranges)
//We now have both the hex data and the post signing flash erase block data
//in two RAM buffers. Compare them to each other to perform post-signing
//verify.
for(i = 0; i < extendedBootInfo.PIC18.erasePageSize; i++)
{
if(flashData[i] != hexEraseBlockData[i])
{
failureDetected = true;
qWarning("Post signing verify failure.");
EraseDevice(); //Send an erase command, to forcibly
//remove the signature (which might be valid), since
//there was a verify error and we can't trust the application
//firmware image integrity. This ensures the device jumps
//back into bootloader mode always.
errorAddress = startOfEraseBlock + i;
expectedResult = hexEraseBlockData[i] + ((uint32_t)hexEraseBlockData[i+1] << 8);
//expectedResult = hexEraseBlockData[i];
actualResult = flashData[i] + ((uint32_t)flashData[i+1] << 8);
//actualResult = flashData[i];
break;
}
}//for(i = 0; i < extendedBootInfo.PIC18.erasePageSize; i++)
}//if(device->family == Device::PIC18)
}//if(deviceFirmwareIsAtLeast101 == true)
}//if(failureDetected == false)
if(failureDetected == true)
{
qDebug("Verify failed at address: 0x%x", errorAddress);
qDebug("Expected result: 0x%x", expectedResult);
qDebug("Actual result: 0x%x", actualResult);
emit AppendString("Operation aborted due to error encountered during verify operation.");
emit AppendString("Please try the erase/program/verify sequence again.");
emit AppendString("If repeated failures are encountered, this may indicate the flash");
emit AppendString("memory has worn out, that the device has been damaged, or that");
emit AppendString("there is some other unidentified problem.");
emit IoWithDeviceCompleted("Verify", Comm::Fail, ((double)elapsed.elapsed()) / 1000);
//Set flags only used when the program was invoked from the console with extra parameters for auto-program and close behavior.
consoleEraseProgramVerifiedOkay = false;
consoleInvokedEraseProgramVerifyComplete = true;
}
else
{
emit IoWithDeviceCompleted("Verify", Comm::Success, ((double)elapsed.elapsed()) / 1000);
emit AppendString("Device verified successfully.");
//Set flags only used when the program was invoked from the console with extra parameters for auto-program and close behavior.
consoleEraseProgramVerifiedOkay = true;
consoleInvokedEraseProgramVerifyComplete = true;
}
return failureDetected ? Comm::Fail : Comm::Success;
}//void MainWindow::VerifyDevice()
//This thread programs previously parsed .hex file data into the device's programmable memory regions.
Comm::ErrorCode MainWindow::WriteDevice(void)
{
QTime elapsed;
Comm::ErrorCode result;
DeviceData::MemoryRange hexRange;
//Now begin re-programming each section based on the info we obtained when
//we parsed the user's .hex file.
// NOTE: The bootloader allows writing config bits. This functionality remains in
// the bootloader firmware but has been removed from this utility because the end user
// will never need to use it.
// NOTE 2: The original version of this utility provided a settings dialog to select which
// sections of flash memory should be written. This has been removed and this utility will
// instead always write only program flash. The ability to write EEPROM has been retained,
// however it is unused.
emit IoWithDeviceStarted("Writing Device...");
elapsed.start();
foreach(hexRange, hexData->ranges)
{
if(hexRange.type == PROGRAM_MEMORY)
{
result = comm->Program(hexRange.start,
device->bytesPerPacket,
device->bytesPerAddressFLASH,
device->bytesPerWordFLASH,
device->family,
hexRange.end,
hexRange.pDataBuffer);
}
else if(writeEeprom && (hexRange.type == EEPROM_MEMORY))
{
result = comm->Program(hexRange.start,
device->bytesPerPacket,
device->bytesPerAddressEEPROM,
device->bytesPerWordEEPROM,
device->family,
hexRange.end,
hexRange.pDataBuffer);
}
else
{
continue;
}
if(result != Comm::Success)
{
qWarning("Programming failed");
return result;
}
}
emit IoWithDeviceCompleted("Write", result, ((double)elapsed.elapsed()) / 1000);
return Comm::Success;
}
2019-08-19 16:56:38 -04:00
void MainWindow::on_checkBoxAdvancedMode_stateChanged(int state)
{
int heightShrinkSize = ui->groupBoxAdvanced->height() + ui->groupBoxAdvanced->parentWidget()->layout()->spacing();
QSize cWidgetFinalSize(ui->centralWidget->width(), ui->centralWidget->height() - heightShrinkSize);
QSize windowFinalSize(width(), height() - heightShrinkSize);
2019-08-19 16:56:38 -04:00
ui->groupBoxAdvanced->setVisible(state != 0);
ui->centralWidget->updateGeometry();
2019-08-19 16:56:38 -04:00
// handle adjustment of window height when advanced controls are hidden
if (state == 0)
{
QTimer::singleShot(0, ui->centralWidget, [this,cWidgetFinalSize,windowFinalSize]() {
ui->centralWidget->resize(cWidgetFinalSize);
resize(windowFinalSize);
});
2019-08-19 16:56:38 -04:00
}
}
Comm::ErrorCode MainWindow::EraseDevice(void)
{
QTime elapsed;
Comm::ErrorCode result;
Comm::BootInfo bootInfo;
emit IoWithDeviceStarted("Erasing Device... (no status update until complete, may take several seconds)");
elapsed.start();
result = comm->Erase();
if(result != Comm::Success)
{
emit IoWithDeviceCompleted("Erase", result, ((double)elapsed.elapsed()) / 1000);
return result;
}
result = comm->ReadBootloaderInfo(&bootInfo);
emit IoWithDeviceCompleted("Erase", result, ((double)elapsed.elapsed()) / 1000);
return result;
}
//Executes when the user clicks the open hex file button on the main form.
2019-08-17 12:33:27 -04:00
void MainWindow::on_btnFwBrowse_clicked()
{
QString msg, newFileName;
QTextStream stream(&msg);
//Create an open file dialog box, so the user can select a .hex file.
newFileName =
QFileDialog::getOpenFileName(this, "Open Hex File", fileName, "Hex Files (*.hex *.ehx)");
if(newFileName.isEmpty())
{
return;
}
ui->txtFirmwareFile->setText(newFileName);
LoadFile(newFileName);
}
void MainWindow::LoadFile(QString newFileName)
{
QString msg;
QTextStream stream(&msg);
QFileInfo nfi(newFileName);
QApplication::setOverrideCursor(Qt::BusyCursor);
HexImporter import;
HexImporter::ErrorCode result;
int i;
hexData->ranges.clear();
//Print some debug info to the debug window.
qDebug("Total Programmable Regions Reported by Device: %d", deviceData->ranges.count());
//First duplicate the deviceData programmable region list and
//allocate some RAM buffers to hold the hex data that we are about to import.
for(i = 0; i < deviceData->ranges.count(); i++)
{
//Allocate some RAM for the hex file data we are about to import.
//Initialize all bytes of the buffer to 0xFF, the default unprogrammed memory value,
//which is also the "assumed" value, if a value is missing inside the .hex file, but
//is still included in a programmable memory region.
DeviceData::MemoryRange HexBuffer = deviceData->ranges[i];
//Allocate a new RAM buffer to hold the .hex file contents associated with the programmable region in the microcontroller.
HexBuffer.pDataBuffer = new unsigned char[deviceData->ranges[i].dataBufferLength];
//Initialize the assumed .hex contents buffer to 0xFF, or the values read from the microcontroller initally.
//Any contents found in the .hex will subsequently overwrite these values with the new values from the .hex file.
memset(HexBuffer.pDataBuffer, 0xFF, HexBuffer.dataBufferLength);
if(deviceData->ranges[i].type == CONFIG_MEMORY)
{
//Initialize our local RAM buffer (intended for storing .hex file read in data) with
//values that we obtained by reading from the device.
//When the .hex file is opened/parsed, the values that overlap with what we read from
//the device, but also existin the .hex file, will get overwritten
//with the values from the hex file. However, values that are not in the .hex file, will
//remain the same as what we read from the device initially, prior to an erase/program/verify
//operation. This is done, since it is possible to have a .hex file that is incomplete, and
//only includes some config bit values, but not others (which may still be in the silicon, or could be an unimplemented location).
//In these cases, in these special locations, during the erase/program/verify, we want to program into the device
//the same values that already exist in the silicon, at these extra locations.
if(deviceData->ranges[i].pDataBuffer != 0)
{
unsigned int copySize;
copySize = deviceData->ranges[i].dataBufferLength;
if(HexBuffer.dataBufferLength < copySize)
{
copySize = HexBuffer.dataBufferLength;
}
memcpy(HexBuffer.pDataBuffer, deviceData->ranges[i].pDataBuffer, copySize);
}
//comm->GetData(bootInfo.memoryRegions[i].address, bootInfo.bytesPerPacket, device->bytesPerAddressConfig, device->bytesPerWordConfig, bootInfo.memoryRegions[i].address + (range.dataBufferLength / device->bytesPerAddressConfig), &range.pDataBuffer[0]);
}
hexData->ranges.append(HexBuffer);
//Print info regarding the programmable memory region to the debug window.
qDebug("Device Programmable Region: [0x%X - 0x%X]", HexBuffer.start, HexBuffer.end);
}
//Import the hex file data into the hexData->ranges[].pDataBuffer buffers.
result = import.ImportHexFile(newFileName, hexData, device);
//Based on the result of the hex file import operation, decide how to proceed.
switch(result)
{
case HexImporter::Success:
break;
case HexImporter::CouldNotOpenFile:
QApplication::restoreOverrideCursor();
stream << "Error: Could not open file " << nfi.fileName() << "\n";
ui->plainTextEdit->appendPlainText(msg);
return;
case HexImporter::NoneInRange:
QApplication::restoreOverrideCursor();
stream << "No address within range in file: " << nfi.fileName() << ". Verify the correct firmware image was specified and is designed for your device.\n";
ui->plainTextEdit->appendPlainText(msg);
return;
case HexImporter::ErrorInHexFile:
QApplication::restoreOverrideCursor();
stream << "Error in hex file. Please make sure the firmware image supplied was designed for the device to be programmed. \n";
ui->plainTextEdit->appendPlainText(msg);
return;
case HexImporter::InsufficientMemory:
QApplication::restoreOverrideCursor();
stream << "Memory allocation failed. Please close other applications to free up system RAM and try again. \n";
ui->plainTextEdit->appendPlainText(msg);
return;
default:
QApplication::restoreOverrideCursor();
stream << "Failed to import: " << result << "\n";
ui->plainTextEdit->appendPlainText(msg);
return;
}
fileName = newFileName;
watchFileName = newFileName;
stream.setIntegerBase(10);
msg.clear();
QFileInfo fi(fileName);
QString name = fi.fileName();
stream << "Opened: " << name << "\n";
ui->plainTextEdit->appendPlainText(msg);
ui->lblFlashStatus->setText("Ready.");
hexOpen = true;
setBootloadEnabled(true);
QApplication::restoreOverrideCursor();
return;
}
void MainWindow::openRecentFile(void)
{
QAction *action = qobject_cast<QAction *>(sender());
if (action)
{
LoadFile(action->data().toString());
}
}
void MainWindow::GetQuery()
{
QTime totalTime;
Comm::BootInfo bootInfo;
DeviceData::MemoryRange range;
QString connectMsg;
QTextStream ss(&connectMsg);
qDebug("Executing GetQuery() command.");
totalTime.start();
if(!comm->isConnected())
{
qWarning("Query not sent, device not connected");
return;
}
//Send the Query command to the device over USB, and check the result status.
switch(comm->ReadBootloaderInfo(&bootInfo))
{
case Comm::Fail:
case Comm::IncorrectCommand:
ui->plainTextEdit->appendPlainText("Unable to communicate with device\n");
return;
case Comm::Timeout:
ss << "Operation timed out";
break;
case Comm::Success:
wasBootloaderMode = true;
ss << "Device Ready";
ui->lblDeviceConnected->setText("Connected");
ui->lblDeviceConnected->setStyleSheet("QLabel { background-color : green; }");
ui->lblDeviceStatusMsg->setText("");
break;
default:
return;
}
ss << " (" << (double)totalTime.elapsed() / 1000 << "s)\n";
ui->plainTextEdit->appendPlainText(connectMsg);
deviceData->ranges.clear();
//Now start parsing the bootInfo packet to learn more about the device. The bootInfo packet contains
//contains the query response data from the USB device. We will save these values into globabl variables
//so other parts of the application can use the info when deciding how to do things.
device->family = (Device::Families) bootInfo.deviceFamily;
device->bytesPerPacket = bootInfo.bytesPerPacket;
//Set some processor family specific global variables that will be used elsewhere (ex: during program/verify operations).
switch(device->family)
{
case Device::PIC18:
device->bytesPerWordFLASH = 2;
device->bytesPerAddressFLASH = 1;
break;
default:
device->bytesPerWordFLASH = 2;
device->bytesPerAddressFLASH = 1;
break;
}
//Initialize the deviceData buffers and length variables, with the regions that the firmware claims are
//reprogrammable. We will need this information later, to decide what part(s) of the .hex file we
//should look at/try to program into the device. Data sections in the .hex file that are not included
//in these regions should be ignored.
for(int i = 0; i < MAX_DATA_REGIONS; i++)
{
if(bootInfo.memoryRegions[i].type == END_OF_TYPES_LIST)
{
//Before we quit, check the special versionFlag byte,
//to see if the bootloader firmware is at least version 1.01.
//If it is, then it will support the extended query command.
//If the device is based on v1.00 bootloader firmware, it will have
//loaded the versionFlag location with 0x00, which was a pad byte.
if(bootInfo.versionFlag == BOOTLOADER_V1_01_OR_NEWER_FLAG)
{
deviceFirmwareIsAtLeast101 = true;
qDebug("Device bootloader firmware is v1.01 or newer and supports Extended Query.");
//Now fetch the extended query information packet from the USB firmware.
comm->ReadExtendedQueryInfo(&extendedBootInfo);
qDebug("Device bootloader firmware version is: %d", extendedBootInfo.PIC18.bootloaderVersion);
}
else
{
deviceFirmwareIsAtLeast101 = false;
}
break;
}
//Error check: Check the firmware's reported size to make sure it is sensible. This ensures
//we don't try to allocate ourselves a massive amount of RAM (capable of crashing this PC app)
//if the firmware claimed an improper value.
if(bootInfo.memoryRegions[i].size > MAXIMUM_PROGRAMMABLE_MEMORY_SEGMENT_SIZE)
{
bootInfo.memoryRegions[i].size = MAXIMUM_PROGRAMMABLE_MEMORY_SEGMENT_SIZE;
}
//Parse the bootInfo response packet and allocate ourselves some RAM to hold the eventual data to program.
if(bootInfo.memoryRegions[i].type == PROGRAM_MEMORY)
{
range.type = PROGRAM_MEMORY;
range.dataBufferLength = bootInfo.memoryRegions[i].size * device->bytesPerAddressFLASH;
range.pDataBuffer = new unsigned char[range.dataBufferLength];
memset(&range.pDataBuffer[0], 0xFF, range.dataBufferLength);
}
else if(bootInfo.memoryRegions[i].type == EEPROM_MEMORY)
{
range.type = EEPROM_MEMORY;
range.dataBufferLength = bootInfo.memoryRegions[i].size * device->bytesPerAddressEEPROM;
range.pDataBuffer = new unsigned char[range.dataBufferLength];
memset(&range.pDataBuffer[0], 0xFF, range.dataBufferLength);
}
else if(bootInfo.memoryRegions[i].type == CONFIG_MEMORY)
{
range.type = CONFIG_MEMORY;
range.dataBufferLength = bootInfo.memoryRegions[i].size * device->bytesPerAddressConfig;
range.pDataBuffer = new unsigned char[range.dataBufferLength];
memset(&range.pDataBuffer[0], 0xFF, range.dataBufferLength);
//Read in the current config bit values from the device, and initialize our local RAM
//buffer to contain the same values. When the .hex file is opened/parsed, the values
//that overlap with what we read from the device and what is in the .hex file will get overwritten
//with the values from the hex file. However, values that are not in the .hex file, will
//remain the same as what we read from the device initially, prior to an erase/program/verify
//operation. This is done, since it is possible to have a .hex file that is incomplete, and
//only include some config bit values, but not others. In these cases, we want to program into
//the device the same values for these extra locations (not in the .hex, but present in the silicon),
//as were present in the device, prior to doing the erase/program/verify operation.
comm->GetData(bootInfo.memoryRegions[i].address, bootInfo.bytesPerPacket, device->bytesPerAddressConfig, device->bytesPerWordConfig, bootInfo.memoryRegions[i].address + (range.dataBufferLength / device->bytesPerAddressConfig), &range.pDataBuffer[0]);
}
//Notes regarding range.start and range.end: The range.start is defined as the starting address inside
//the USB device that will get programmed. For example, if the bootloader occupies 0x000-0xFFF flash
//memory addresses (ex: on a PIC18), then the starting bootloader programmable address would typically
//be = 0x1000 (ex: range.start = 0x1000).
//The range.end is defined as the last address that actually gets programmed, plus one, in this programmable
//region. For example, for a 64kB PIC18 microcontroller, the last implemented flash memory address
//is 0xFFFF. If the last 1024 bytes are reserved by the bootloader (since that last page contains the config
//bits for instance), then the bootloader firmware may only allow the last address to be programmed to
//be = 0xFBFF. In this scenario, the range.end value would be = 0xFBFF + 1 = 0xFC00.
//When this application uses the range.end value, it should be aware that the actual address limit of
//range.end does not actually get programmed into the device, but the address just below it does.
//In this example, the programmed region would end up being 0x1000-0xFBFF (even though range.end = 0xFC00).
//The proper code to program this would basically be something like this:
//for(i = range.start; i < range.end; i++)
//{
// //Insert code here that progams one device address. Note: for PIC18 this will be one byte for flash memory.
// //For PIC24 this is actually 2 bytes, since the flash memory is addressed as a 16-bit word array.
//}
//In the above example, the for() loop exits just before the actual range.end value itself is programmed.
range.start = bootInfo.memoryRegions[i].address;
range.end = bootInfo.memoryRegions[i].address + bootInfo.memoryRegions[i].size;
//Add the new structure+buffer to the list
deviceData->ranges.append(range);
}
ui->lblDeviceBootloaderVer->setText(QString("%1").arg(extendedBootInfo.PIC18.bootloaderVersion, 4, 16, QChar('0')));
QString appVerMsg = (extendedBootInfo.PIC18.applicationVersion != 0xffff) ?
QString("%1").arg(extendedBootInfo.PIC18.applicationVersion, 4, 16, QChar('0')) :
"No FW on device";
ui->lblDeviceAppFWVer->setText(appVerMsg);
// if the user already loaded a hex file, attempt to load it again for the newly connected device
if (!ui->txtFirmwareFile->text().isEmpty())
LoadFile(ui->txtFirmwareFile->text());
setBootloadEnabled(true);
}
void MainWindow::on_btnResetDevice_clicked()
{
if(!comm->isConnected())
{
failed = -1;
qWarning("Reset not sent, device not connected");
return;
}
ui->plainTextEdit->appendPlainText("Resetting...");
comm->Reset();
}