/*
STM32 HID Bootloader - USB HID bootloader for STM32F10x and STM32F4xx
Copyright 2018 Bruno Freitas - bruno@brunofreitas.com
20 April 2018 Vassilis Serasidis <avrsite@yahoo.gr>
*211007 Reduced code size + smell, COM port now optional
+240201 Support for STM32F401 preprogrammed WeAct BL added and verified
This HID bootloader work with bluepill + STM32duino + Arduino IDE <http://www.stm32duino.com/>
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "rs232.h"
#include "hidapi.h"
#define SECTOR_SIZE 1024
#define VID_PID_103 0xBEBA1209 // My STM32F103 HID bootloader
#define VID_PID_401 0x572A0483 // STM32F401 on black pill bootloader
#define FIRMWARE_VER 0x0300
#ifndef _WIN32
# define _cdecl
# include <unistd.h>
# define _snprintf snprintf
void Sleep(unsigned ms) {usleep(ms*1000);}
#endif
static const char*gComName;
#ifdef _WIN32
static UINT consoleCP=CP_OEMCP;
#endif
static void hyphen() {
#ifdef _WIN32
printf(consoleCP==CP_UTF8 ? " — " : " -- ");
#else
printf(" — ");
#endif
}
static void done(bool ok) {
hyphen();
puts(ok?"Done.":"Error!");
}
// The sole usage for the serial port is resetting the STM32
// and to activate the HID bootloader.
// This saves user interaction, otherwise, RESET button must be pressed.
static bool serial_init(char*name) {
unsigned ms=0;
char*dp=strrchr(name,':');
if (dp) {
*dp=0; // make COM port name without ':' part
ms=strtoul(++dp,0,0); // get delay value
}
gComName=name;
printf("> Open \"%s\"\n",name);
RS232 port(name);
if (!port) return false;
printf("> Toggling DTR...");
port.disableRTS();
port.enableDTR();
Sleep(200);
port.disableDTR();
Sleep(200);
port.enableDTR();
Sleep(200);
port.disableDTR();
Sleep(200);
port.send_magic();
Sleep(200);
Sleep(ms);
done(true);
return true;
}
static int memstrcmp(const void*mem, const char*str) {return memcmp(mem,str,strlen(str));}
static void memstrcpy(void*mem,const char*str,char suffix) {
size_t l=strlen(str);
memcpy(mem,str,l);
((char*)mem)[l]=suffix;
}
typedef unsigned char byte;
// subclass to fill the virtual match() procedure
class MyHidDev:public HidDev{
bool match() const;
public:
void retry_msg() const;
bool write(byte*buffer);
};
bool MyHidDev::match() const{
static const uint32_t vidpids[]={VID_PID_103,VID_PID_401};
//wprintf(L"Product/Vendor: %x %x\n", attrib.ProductID, attrib.VendorID);
for (int i=0; i<elemof(vidpids); i++)
if (vidpid()==vidpids[i]) return true;
return false;
}
bool MyHidDev::write(byte*buffer) {
return setOutputReport(buffer,100); // many attempts
}
void MyHidDev::retry_msg() const{
static byte k;
putchar("\\|/-"[k&3]); k++; putchar('\b'); fflush(stdout);
Sleep(100);
}
int _cdecl main(int argc, char *argv[]) {
static const char BTLDCMD[] = "BTLDCMD"; // original (not mine) Serasidis / Arduino bootloader on STM32F103
static const char WEACT[] = "WeAct:"; // magic seen on STM32F401 black pill board: Chinese boot loader
#ifdef _WIN32
static const char COM1[] = "COM";
static const char COM2[] = "com";
if (SetConsoleOutputCP(CP_UTF8)) consoleCP=CP_UTF8;
#else
static const char COM1[] = "ttyS";
static const char COM2[] = "ttyUSB";
#endif
int error = 0;
MyHidDev dev;
byte*bufO=0,*bufI=0,*bufF=0;
if (argc<2) { // this box contain tabs, tab size should be 8 in your damn editor, what else!
fputs("┌",stdout); int l=63; do fputs("─",stdout); while (--l); puts("┐");
puts("│ HID-Flash v2.2.4 - STM32 driverless HID Bootloader Flash Tool │\n"
"│ (c) 2018 - Bruno Freitas www.brunofreitas.com │\n"
"│ (c) 2018-2019 - Vassilis Serasidis www.serasidis.gr │\n"
"│ Customized for STM32duino ecosystem www.stm32duino.com │\n"
"│ 2021-2024 - Henrik Haftmann www.tu-chemnitz.de/~heha│");
fputs("└",stdout); l=63; do fputs("─",stdout); while (--l); puts("┘");
puts("Usage: hid-flash <binary_firmware_file> [comport[:delay-ms]]");
puts(" hid-flash start | erase");
return 1;
}
setbuf(stdout,0);
FILE*firmware_file = 0;
bool start=false, erase=false, weact=false, resetpagesSent=false;
for (char**argp=argv+1;;argp++) {
char*arg=*argp;
if (!arg) break; // no more command-line arguments
if (!strcmp(arg,"start")) start=true;
else if (!strcmp(arg,"erase")) erase=true;
else{
#ifdef UNICODE // arg may contain non-ASCII characters, so use wide-char open function
int l=MultiByteToWideChar(CP_UTF8,0,arg,-1,0,0); // l counts '\0' too
wchar_t*n=new wchar_t[l];
MultiByteToWideChar(CP_UTF8,0,arg,-1,n,l);
firmware_file = _wfopen(n,L"rb");
delete[]n;
#else
firmware_file = fopen(arg,"rb");
#endif
if (!firmware_file) {
printf("> Error opening firmware file %s\n", arg);
return error;
}
if (argp[1] // next argument given, and looking like a COM port?
&& (strstr(COM1,argp[1]) || strstr(COM2,argp[1]))) {
arg=*++argp; // eat up
if (!serial_init(arg)) printf("> Unable to open %s\n",arg);
}
}
// 240201: Now, multiple arguments may be given on command line
// The first processed arg opens the HID device.
if (!dev) {
printf("> Searching for STM32 device");
if (dev.connect()) { // search first occurence
if (dev.vidpid()==VID_PID_103
&& (!dev.caps.FeatureReportByteLength // Feature reports show new firmware
|| dev.VersionNumber() < FIRMWARE_VER)) { //The STM32 board has firmware lower than 3.00
hyphen();
puts("Error!");
puts("\tPlease update the firmware to the latest version (v3.00+)");
goto exit;
}
}else{
hyphen();
puts("Not found!");
error = 1;
goto exit;
}
hyphen();
printf("found [%04X:%04X].\n",LOWORD(dev.vidpid()),HIWORD(dev.vidpid()));
if (dev.caps.OutputReportByteLength!=65) {
puts("Output report size must be 64 (plus one for the report ID byte)!");
goto exit;
}
bufO=new byte[dev.caps.OutputReportByteLength];
memset(bufO,0,sizeof bufO);
bufI=new byte[dev.caps.InputReportByteLength];
bufF=new byte[dev.caps.FeatureReportByteLength];
if (dev.vidpid()==VID_PID_401) {
memstrcpy(bufO+1,WEACT,2); // 2 = get version string
dev.write(bufO);
if (dev.getInputReport(bufI,1,200)
&& !memstrcmp(bufI+1,WEACT)) {
printf("> Version string is \"%s\".\n",bufI+1);
weact=true;
}
}
}
if (erase) {
if (weact) {
printf("> Erase flash");
memstrcpy(bufO+1,WEACT,4);
dev.write(bufO);
bool ok=dev.getInputReport(bufI,50,200); // may take a while, emits '.' on each retry
done(ok);
}else puts("> \"erase\" is only available on WeAct boot loader, parameter ignored.");
erase=false;
}
if (firmware_file) {
bool ok=true;
unsigned filesize = 0; // filesize zero denotes binary data from pipe
if (!fseek(firmware_file,0,SEEK_END)) {
filesize = ftell(firmware_file);
fseek(firmware_file,0,SEEK_SET);
if (filesize<16) {
printf("> firmware file %s is empty!\n",arg);
ok=false;
}
}
// Only for the first binary file
if (ok && !resetpagesSent) {
printf("> Initialize flashing");
// Send RESET PAGES command to put HID bootloader in initial stage...
if (dev.caps.FeatureReportByteLength) { // Firmware with feature reports
bufF[0]=0;
bufF[1]=1;
ok=dev.setFeatureReport(bufF);
}else{
memstrcpy(bufO+1,weact?WEACT:BTLDCMD,0);
// Flash is unavailable when writing to it, so USB interrupt may fail here
ok=dev.write(bufO);
}
resetpagesSent=ok;
done(ok);
}
if(ok) {
// Send Firmware File data
const char*sizestr="unknown";
int w=6; // field width (= digits to reserve) for progress
if (filesize) {
char buf[8];
w=(int)_snprintf(buf,sizeof buf,"%u",(filesize+SECTOR_SIZE-1)&~(SECTOR_SIZE-1));
_snprintf(buf,sizeof buf,"%u",filesize);
sizestr=buf;
}
printf("> Flashing firmware, size = %s, current = %*u",sizestr,w,0);
fflush(stdout);
for (int bw=0;;) {
int br,k;
for(br=0;br<64;br+=k) { // For stream input read until chunk is full or EOF occurs
k=(int)fread(bufO+1+br,1,64-br,firmware_file);
if (k<0) {br=k; break;} // error, bail out with negative result
if (!k) break; // end-of-stream, return bytes read so far
}
if (br<0) break; // error reading file
memset(bufO+1+br,0xFF,64-br); // clear rest-of-buffer
if (!dev.write(bufO)) break;
bw+=64;
if (!(bw&SECTOR_SIZE-1)) { // reached one sector: expect answer
for(int k=0;k<w;k++) putchar('\b');
printf("%*u",w,bw); // update byte count in sector intervals (may be larger than file size)
fflush(stdout);
ok=dev.getInputReport(bufI,1,500); // wait upto 0.5 s for answer after flashing
if (!ok) break;
if (dev.caps.FeatureReportByteLength) { // modern driver? 1-Byte answer
if (bufI[1]!=1) {ok=false; break;} // bail out when wrong
}else if (weact) { // Serasidis driver? 8-Byte answer
if (memstrcmp(bufI+1,WEACT) || bufI[7]!=3) {ok=false; break;}
}else{
if (memstrcmp(bufI+1,BTLDCMD) || bufI[8]!=2) {ok=false; break;} // bail out when wrong
}
if (br<64) break; // end of file or inputstream reached, leave loop
}
}
done(ok);
}
if (ok) start=true; else error = 1;
fclose(firmware_file);
firmware_file=0;
}
}// for loop
if (dev && start) {
// Send CMD_REBOOT_MCU command to reboot the microcontroller...
bool ok;
printf("> Reboot STM32 controller and start application");
if (dev.caps.FeatureReportByteLength) {
bufF[0]=0;
bufF[1]=2;
ok=dev.setFeatureReport(bufF);
}else{
memstrcpy(bufO+1,weact?WEACT:BTLDCMD,1);
// Flash is unavailable when writing to it, so USB interrupt may fail here
ok=dev.write(bufO);
}
done(ok);
}
exit:
if (bufF)delete[]bufF;
if (bufI)delete[]bufI;
if (bufO)delete[]bufO;
// Dirty incomprehensible hack
if (gComName) {
printf("> Searching for %s",gComName);
int i;
for (i=0;i<5;i++){
RS232 port(gComName);
if (port) {
hyphen();
puts("found.");
break;
}
dev.retry_msg();
}
if(i==5) {
hyphen();
puts("Not found!");
}
}
return error;
}
#ifdef _WIN32
// To reduce executable file size by whopping 30 KB,
// this startup code feeds main() with arguments.
// Initializing RAM with DATA, BSS, HEAP and STACK
// is done by Windows executable loader.
// As this program doesn't use static objects,
// invoking their constructors is not needed here.
# ifdef UNICODE // NT based systems, 32 and 64 bit
void mainCRTStartup() {
int argc;
wchar_t**argvW=CommandLineToArgvW(GetCommandLine(),&argc);
char**argv=new char*[argc+1],**argvP=argv;
for (int i=0; i<argc; i++,argvP++) {
int len=WideCharToMultiByte(CP_UTF8,0,argvW[i],-1,0,0,0,0);
*argvP=new char[len];
WideCharToMultiByte(CP_UTF8,0,argvW[i],-1,*argvP,len,0,0);
}
*argvP=0;
LocalFree(argvW);
ExitProcess(main(argc,argv));
}
# else
// In case of Windows 98/Me, there is no Windows helper
// function to dissect the command line.
// So use the dissect routine inside msvcrt.dll.
EXTERN_C _CRTIMP int __cdecl __getmainargs(int&,char**&,char**&,int);
void mainCRTStartup() {
int argc;
char**argv,**envp;
__getmainargs(argc,argv,envp,TRUE);
ExitProcess(main(argc,argv));
}
# endif
#endif
Vorgefundene Kodierung: UTF-8 | 0
|