@echo off setlocal @REM ============================================================ @REM Keil AfterMake: copy HEX and generate BIN after each build @REM Working dir may be MDK-ARM, so always switch to script dir @REM ============================================================ cd /d "%~dp0" @REM Executable name (same as Keil OutputName) set HEX_NAME=Project @REM Keil output directory set HEX_PATH=%cd%\MDK-ARM\Objects @REM Custom output directory set OUTPUT_PATH=%cd%\Output @REM Project code: application.h __PROJECT_CODE__ @REM Soft version: jb_product.h JB_MCU_SW_VERSION_* (single source of truth) set APP_H=%cd%\Inc\application.h set PRODUCT_H=%cd%\Protocol\jb_product.h @REM Optional arg1: Keil root ($K) passed by AfterMake set "KEIL_ROOT=%~1" @REM ------------------------------------------------------------ @REM Timestamp: yyMMdd_HHmmss @REM ------------------------------------------------------------ for /f %%i in ('powershell -NoProfile -Command "Get-Date -Format yyMMdd_HHmmss"') do set CURRENT_DATE=%%i @REM ------------------------------------------------------------ @REM Compose name: PROJECT_MAJOR.MINOR.PATCH @REM ------------------------------------------------------------ set PROJECT_CODE=DEMO set VER_MAJOR=0 set VER_MINOR=0 set VER_PATCH=0 for /f "tokens=3 delims= " %%i in ('findstr /C:"#define __PROJECT_CODE__" "%APP_H%"') do set PROJECT_CODE=%%i if defined PROJECT_CODE set PROJECT_CODE=%PROJECT_CODE:"=% for /f "tokens=3 delims= " %%i in ('findstr /C:"#define JB_MCU_SW_VERSION_MAJOR" "%PRODUCT_H%"') do set VER_MAJOR=%%i for /f "tokens=3 delims= " %%i in ('findstr /C:"#define JB_MCU_SW_VERSION_MINOR" "%PRODUCT_H%"') do set VER_MINOR=%%i for /f "tokens=3 delims= " %%i in ('findstr /C:"#define JB_MCU_SW_VERSION_PATCH" "%PRODUCT_H%"') do set VER_PATCH=%%i set SW_Ver=%PROJECT_CODE%_%VER_MAJOR%.%VER_MINOR%.%VER_PATCH% set output_file_name=%SW_Ver%_%CURRENT_DATE% echo Firmware version: %SW_Ver% if not exist "%OUTPUT_PATH%" mkdir "%OUTPUT_PATH%" @REM ------------------------------------------------------------ @REM Copy HEX @REM ------------------------------------------------------------ if exist "%HEX_PATH%\%HEX_NAME%.hex" ( echo Output hex file: %OUTPUT_PATH%\%output_file_name%.hex copy /Y "%HEX_PATH%\%HEX_NAME%.hex" "%OUTPUT_PATH%\%output_file_name%.hex" >nul if errorlevel 1 ( echo [ERROR] Failed to copy HEX ) else ( echo [OK] HEX copied ) ) else ( echo [ERROR] HEX not found: %HEX_PATH%\%HEX_NAME%.hex ) @REM ------------------------------------------------------------ @REM Generate BIN via fromelf (AXF), fallback: HEX to BIN @REM ------------------------------------------------------------ set "FROMELF=" set "BIN_FILE=%HEX_PATH%\%HEX_NAME%.bin" call :find_fromelf if not "%FROMELF%"=="" if exist "%HEX_PATH%\%HEX_NAME%.axf" ( echo Using fromelf: %FROMELF% "%FROMELF%" --bincombined --output="%BIN_FILE%" "%HEX_PATH%\%HEX_NAME%.axf" if errorlevel 1 ( echo [WARN] fromelf --bincombined failed, try --bin "%FROMELF%" --bin --output="%BIN_FILE%" "%HEX_PATH%\%HEX_NAME%.axf" ) ) if not exist "%BIN_FILE%" ( echo [WARN] Convert HEX to BIN call :hex2bin ) if not exist "%BIN_FILE%" ( echo [ERROR] BIN not generated exit /b 1 ) echo Output bin file: %OUTPUT_PATH%\%output_file_name%.bin copy /Y "%BIN_FILE%" "%OUTPUT_PATH%\%output_file_name%.bin" >nul if errorlevel 1 ( echo [ERROR] Failed to copy BIN exit /b 1 ) else ( echo [OK] BIN copied ) exit /b 0 @REM ============================================================ @REM Find fromelf.exe @REM ============================================================ :find_fromelf if not "%KEIL_ROOT%"=="" ( if exist "%KEIL_ROOT%\ARM\ARM_Compiler_5.06u7\bin\fromelf.exe" ( set "FROMELF=%KEIL_ROOT%\ARM\ARM_Compiler_5.06u7\bin\fromelf.exe" goto :eof ) if exist "%KEIL_ROOT%\ARM\ARMCC\bin\fromelf.exe" ( set "FROMELF=%KEIL_ROOT%\ARM\ARMCC\bin\fromelf.exe" goto :eof ) if exist "%KEIL_ROOT%\ARM\ARMCLANG\bin\fromelf.exe" ( set "FROMELF=%KEIL_ROOT%\ARM\ARMCLANG\bin\fromelf.exe" goto :eof ) ) set "KEIL_ARM=" for /f "tokens=2*" %%a in ('reg query "HKLM\SOFTWARE\WOW6432Node\Keil\Products\MDK" /v Path 2^>nul') do set "KEIL_ARM=%%b" if "%KEIL_ARM%"=="" ( for /f "tokens=2*" %%a in ('reg query "HKLM\SOFTWARE\Keil\Products\MDK" /v Path 2^>nul') do set "KEIL_ARM=%%b" ) if not "%KEIL_ARM%"=="" ( if exist "%KEIL_ARM%\ARM_Compiler_5.06u7\bin\fromelf.exe" ( set "FROMELF=%KEIL_ARM%\ARM_Compiler_5.06u7\bin\fromelf.exe" goto :eof ) if exist "%KEIL_ARM%\ARMCC\bin\fromelf.exe" ( set "FROMELF=%KEIL_ARM%\ARMCC\bin\fromelf.exe" goto :eof ) if exist "%KEIL_ARM%\ARMCLANG\bin\fromelf.exe" ( set "FROMELF=%KEIL_ARM%\ARMCLANG\bin\fromelf.exe" goto :eof ) ) where fromelf.exe >nul 2>&1 if not errorlevel 1 set "FROMELF=fromelf.exe" goto :eof @REM ============================================================ @REM Fallback: Intel HEX -> BIN (image starts at first data addr) @REM Write a temp ps1 to avoid cmd parenthesis parsing issues @REM ============================================================ :hex2bin if not exist "%HEX_PATH%\%HEX_NAME%.hex" ( echo [ERROR] HEX not found, cannot convert to BIN goto :eof ) set "PS1_FILE=%TEMP%\ota_hex2bin.ps1" > "%PS1_FILE%" echo $hex = $args[0] >> "%PS1_FILE%" echo $bin = $args[1] >> "%PS1_FILE%" echo $map = @{} >> "%PS1_FILE%" echo $min = [uint32]::MaxValue >> "%PS1_FILE%" echo $max = [uint32]0 >> "%PS1_FILE%" echo $ext = [uint32]0 >> "%PS1_FILE%" echo Get-Content -LiteralPath $hex ^| ForEach-Object { >> "%PS1_FILE%" echo if ($_.Length -lt 11) { return } >> "%PS1_FILE%" echo if ($_[0] -ne [char]58) { return } >> "%PS1_FILE%" echo $len = [Convert]::ToInt32($_.Substring(1,2),16) >> "%PS1_FILE%" echo $addr = [Convert]::ToUInt32($_.Substring(3,4),16) >> "%PS1_FILE%" echo $type = [Convert]::ToInt32($_.Substring(7,2),16) >> "%PS1_FILE%" echo if ($type -eq 4) { $ext = [Convert]::ToUInt32($_.Substring(9,4),16) -shl 16; return } >> "%PS1_FILE%" echo if ($type -eq 2) { $ext = [Convert]::ToUInt32($_.Substring(9,4),16) * 16; return } >> "%PS1_FILE%" echo if ($type -ne 0) { return } >> "%PS1_FILE%" echo $fa = $ext + $addr >> "%PS1_FILE%" echo if ($fa -lt $min) { $min = $fa } >> "%PS1_FILE%" echo for ($i = 0; $i -lt $len; $i++) { $map[$fa+$i] = [Convert]::ToByte($_.Substring(9+$i*2,2),16) } >> "%PS1_FILE%" echo if (($fa+$len) -gt $max) { $max = $fa+$len } >> "%PS1_FILE%" echo } >> "%PS1_FILE%" echo if ($max -le $min) { throw 'No HEX data records' } >> "%PS1_FILE%" echo $size = $max - $min >> "%PS1_FILE%" echo $bytes = New-Object byte[] $size >> "%PS1_FILE%" echo for ($i = 0; $i -lt $size; $i++) { $bytes[$i] = 0xFF } >> "%PS1_FILE%" echo foreach ($k in $map.Keys) { $bytes[$k-$min] = $map[$k] } >> "%PS1_FILE%" echo [IO.File]::WriteAllBytes($bin, $bytes) >> "%PS1_FILE%" echo Write-Host ('[OK] HEX converted to BIN, size={0} bytes, base=0x{1:X8}' -f $size, $min) powershell -NoProfile -ExecutionPolicy Bypass -File "%PS1_FILE%" "%HEX_PATH%\%HEX_NAME%.hex" "%BIN_FILE%" goto :eof