1285 lines
58 KiB
C++
1285 lines
58 KiB
C++
/************************************************************************
|
|
* 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);
|
|
|
|
// 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);
|
|
});
|
|
|
|
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;
|
|
}
|
|
|
|
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);
|
|
|
|
ui->groupBoxAdvanced->setVisible(state != 0);
|
|
ui->centralWidget->updateGeometry();
|
|
|
|
// 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);
|
|
});
|
|
}
|
|
}
|
|
|
|
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.
|
|
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();
|
|
}
|