The MCU-Link is an inexpensive CMSIS-DAP debug probe from NXP. It can be used as a GDB server debug probe, and as such it includes scripting support. This scripting can be very useful in some cases where the MCU cannot be accessed by a normal debug session. This happens for example if students are not pay attention what binary they flash to which device, causing an MCU to potentially get ‘bricked’.
Outline
Using a SWD debug connection, it is possible that the debug probe might not be fast enough to regain access to the MCU. By default using the SWD debug protocol, the debug probe tries to reset the MCU, and then halt it. But in cases where the MCU immediately disables the debug block, enters a deep low power mode or anything else disabling access over debug, that device can be ‘bricked’. In the past and for Kinetis devices, I have used a special program provided by PEMICRO (see Recovering Cortex-M Microcontroller with a Power Glitch). But that approach required a P&E debug probe plus and only applies to Kinetis devices.
In this article, I give a short overview how to use scripts with the MCU-Link and Linkserver. Additionally I provide a script I’m using to recover MCU’s which are otherwise not accessible with a normal debug session. If flash security (see How (not) to Secure my Microcontroller) has been enabled, then you might be (officially) out of luck. But otherwise a small recovery script might you get you out.
The idea is to run a script with the debug probe, to catch the MCU right after it is coming out of reset. I’m using here the NXP MCU-Link debug probe, which can be used with an IDE like Eclipse (MCUXpresso IDE) or standalone in command-line mode using the LinkServer software.
LinkServer Scripts
The MCUXpresso IDE Users guide provides documentation about the scripting features:
Example script files can be found inside the following MCUXpresso IDE directory:
<install dir>/ide/binaries/Scripts
The above scripts are used by the LinkServer, e.g. when launching a debug session from Eclipse. I have used them as examples and inspiration for my scripts.
Help Command
Another good source of information is the ‘help’ command within the command line of the standalone LinkServer. Inside the ‘binaries’ sub-folder of the LinkServer installation is a ‘redlinkserv’ executable which has a ‘help’ command:
Launch the tool with
redlinkserv --commandline
In the command line mode the help
redlink>help ********** Probe related functions *********** PROBELIST : Enumerates and returns an indexed list of known probe types PROBENUM : Returns the number of probes attached PROBEOPENBYINDEX <ProbeIndex> [<"FILENAME">] : Opens the probe associated with ProbeIndex FILENAME is text of <key=value> pairs used for internal configuration PROBEOPENBYSERIAL <"SerialNumber"> [<"FILENAME">]: Opens the probe associated with SerialNumber PROBECLOSEBYINDEX <ProbeIndex>: Closes the probe associated with ProbeIndex PROBECLOSEBYSERIAL <"SerialNumber">: Closes the probe associated with SerialNumber PROBEFIRSTFOUND : Returns the THIS ProbeIndex or index of the first probe in the enumerated list PROBETIME <ProbeIndex> : Returns elapsed time from firmware boot, if supported PROBESTATUS [<ProbeIndex>]: Returns an indexed list summary of the status of the probes connected to the system PROBEVERSION <ProbeIndex>: Returns version information about probe firmware PROBEISOPEN <ProbeIndex>: Returns TRUE or FALSE PROBEHASJTAG <ProbeIndex>: Returns TRUE or FALSE PROBEHASSWD <ProbeIndex>: Returns TRUE or FALSE PROBEHASSWV <ProbeIndex>: Returns TRUE or FALSE PROBEHASETM <ProbeIndex>: Returns TRUE or FALSE ********** Core/TAP related functions ****** CORECONFIG {[THIS] | [<ProbeIndex>]}: Queries the scan chain configuration CORESCONFIGURED <ProbeIndex>: Returns TRUE or FALSE APLIMIT {[THIS] | [<ProbeIndex>]}: <APIndex>: Limit the AP Query (set once) APLIST {[THIS] | [<ProbeIndex>]}: [<APLimit>]: Detailed list of APs connected to the specified probe. APLimit restricts queries to AP index. CORELIST {[THIS] | [<ProbeIndex>]}: [<APLimit>]: Detailed list of APs/Cores connected to the specified probe. APLimit restricts queries to AP index. COREREADID {[THIS] | [<ProbeIndex> <CoreIndex>]}: Returns the DpID DEBUGMAILBOXREQ {[THIS] | [<ProbeIndex> <APIndex>]} <Request>: Debug Mailbox Request ********** Wire related functions ********** WIRESWDCONNECT {[THIS] | [<ProbeIndex>]}: Configures the wire for SWD and returns the DpID WIREJTAGCONNECT {[THIS] | [<ProbeIndex>]}: Configures the wire for JTAG WIREDISCONNECT {[THIS] | [<ProbeIndex>]}: Closes the wire connection (SWD/JTAG) WIREISPRESET {[THIS] | [<ProbeIndex>]}: Resets an LPC part into the ISP bootloader WIREBOOTCONFIGSET {[THIS] | [<ProbeIndex>]} <"DATA">: Stores boot configuration data that will be automatically applied during subsequent reset commands. DATA is a string with up to 4 characters describing how each ISP_CTRL[3..0] pin should be handled: '0' (= drive low), '1' (= drive high), 'x' (= do not drive) WIREBOOTCONFIGGET {[THIS] | [<ProbeIndex>]}: Returns previously stored configuration data WIREBOOTCONFIGREAD {[THIS] | [<ProbeIndex>]}: Returns the current state of ISP_CTRL[3:0] pins WIREBOOTCONFIGAPPLY {[THIS] | [<ProbeIndex>]} <1/0>: Immediately starts/stops driving the ISP_CTRL pins based on previously stored boot configuration data WIRETIMEDRESET <ProbeIndex> <ms>: Asserts (Low) reset for ms milliseconds and returns the end state of the wire WIREHOLDRESET <ProbeIndex> <State> : Asserts/Releases (Low/High) reset and returns the end state of the wire WIRESETSPEED <ProbeIndex> <Hz>: Requests a particular wire speed in Hz WIREGETSPEED <ProbeIndex> : Returns the current wire speed WIRESETIDLECYCLES <ProbeIndex> <Cycles>: Sets the number of idle cycles between debug transactions WIREGETIDLECYCLES <ProbeIndex> : Returns the current number of debug idle cycles WIREISCONNECTED <ProbeIndex>: >: Returns TRUE or FALSE if WIRESWDCONNECT or WIREJTAGCONNECT is complete WIREGETPROTOCOL <ProbeIndex>: Returns SWD or JTAG SELECTPROBECORE <ProbeIndex> <CoreIndex> : Sets the THIS parameter Probe/Core pair THIS : Displays the current Probe, Core pair ********** Cortex-M related functions ****** CMINITAPDP {[THIS] | [<ProbeIndex> <CoreIndex>]}: Initialize a CMx core ready for debug connections CMUNINITAPDP {[THIS] | [<ProbeIndex> <CoreIndex>]}: UnInitialize a CMx core (de-assert debug and system power-up) CMWRITEDP {[THIS] | [<ProbeIndex> <CoreIndex>]} <REG> <DATA>: Returns zero on success CMWRITEAP {[THIS] | [<ProbeIndex> <CoreIndex>]} <REG> <DATA>: Returns zero on success CMREADDP {[THIS] | [<ProbeIndex> <CoreIndex>]} <REG>: Returns data CMREADAP {[THIS] | [<ProbeIndex> <CoreIndex>]} <REG>: Returns data (handles RDBUF on AP reads) CMCLEARERRORS {[THIS] | [<ProbeIndex> <CoreIndex>]} CMHALT {[THIS] | [<ProbeIndex> <CoreIndex>]} CMRUN {[THIS] | [<ProbeIndex> <CoreIndex>]} CMSTEP {[THIS] | [<ProbeIndex> <CoreIndex>]} CMREGS {[THIS] | [<ProbeIndex> <CoreIndex>]} CMDEBUGSTATUS {[THIS] | [<ProbeIndex> <CoreIndex>]} CMWRITEREG {[THIS] | [<ProbeIndex> <CoreIndex>]} <RegNumber> <Value> CMREADREG {[THIS] | [<ProbeIndex> <CoreIndex>]} <RegNumber> CMWATCHLIST {[THIS] | [<ProbeIndex> <CoreIndex>]} CMWATCHSET {[THIS] | [<ProbeIndex> <CoreIndex>]} <DWTIndex> <Address> [<RW|R|W>] CMWATCHCLEAR {[THIS] | [<ProbeIndex> <CoreIndex>]} <DWTIndex> CMBREAKLIST {[THIS] | [<ProbeIndex> <CoreIndex>]} : List the FPB breakpoints CMBREAKSET {[THIS] | [<ProbeIndex> <CoreIndex>]} <Address> : Set an FPB CMBREAKCLEAR {[THIS] | [<ProbeIndex> <CoreIndex>]} [<Address>] : Clear an FPB CMSYSRESETREQ {[THIS] | [<ProbeIndex> <CoreIndex>]} : System reset request CMVECTRESETREQ {[THIS] | [<ProbeIndex> <CoreIndex>]} : Core reset request CMRESETVECTORCATCHSET {[THIS] | [<ProbeIndex> <CoreIndex>]} : Enable reset vector catch CMRESETVECTORCATCHCLEAR {[THIS] | [<ProbeIndex> <CoreIndex>]} : Disable reset vector catch ********** BASIC-like Commands ************* PEEK8 {[THIS] | [<ProbeIndex> <CoreIndex>]} <Address> PEEK16 {[THIS] | [<ProbeIndex> <CoreIndex>]} <Address> PEEK32 {[THIS] | [<ProbeIndex> <CoreIndex>]} <Address> POKE8 {[THIS] | [<ProbeIndex> <CoreIndex>]} <Address> <Data> POKE16 {[THIS] | [<ProbeIndex> <CoreIndex>]} <Address> <Data> POKE32 {[THIS] | [<ProbeIndex> <CoreIndex>]} <Address> <Data> QPOKE8 {[THIS] | [<ProbeIndex> <CoreIndex>]} <Address> <Data> QPOKE16 {[THIS] | [<ProbeIndex> <CoreIndex>]} <Address> <Data> QPOKE32 {[THIS] | [<ProbeIndex> <CoreIndex>]} <Address> <Data> QSTARTTRANSFERS {[THIS] | [<ProbeIndex> <CoreIndex>]} <NumReads> MEMDUMP {[THIS] | [<ProbeIndex> <CoreIndex>]} <Byte Address> <Length> MEMLOAD {[THIS] | [<ProbeIndex> <CoreIndex>]} <FileName> <Byte Address> <Length Limit> Loads binary file data to memory MEMSAVE {[THIS] | [<ProbeIndex> <CoreIndex>]} <FileName> <Byte Address> <Length> Saves memory to binary file EXIT: Exit the server PRINT "TEXT"[;[~]Variable | Constant]: Print statement. Prints quoted text and/or value of an internal variable (a%% - z%%), or constant integer expression in decimal, or hexadecimal[~] format TIME : Returns an incrementing centisecond count from the host TIMEMS : Returns an incrementing millisecond count from the host WAIT <msec> : Wait for the number of milliseconds before proceding LIST: Lists a loaded script NEW: Erases a loaded script from memory RENUMBER <Delta>: Renumber script lines with Delta increment (default is 10) LOAD <"FILENAME">: Loads a script from the current, absolute, or relative directory SAVE <"FILENAME">: Saves a script to the current, absolute, or relative directory ********** BASIC-like Keywords For Script Only ** GOTO <LineNumber> IF <relation> THEN <statement> [ELSE <statement>] REPEAT : Start of a repeat block UNTIL <relation> : End with condition of repeat block BREAKREPEATTO <LineNumber> : Premature end of a repeat loop GOSUB <LineNumber> RETURN
💡 Some commands like the LOAD one are used in ‘interactive’ mode. I have not found a way to use ‘load’ in script with a relative path. It works with an absolute path, but that’s not what I want. If I find a solution, I’ll post an update.Running Scripts
Script files can be referenced and called from a launch configuration.
I usually place the ‘main’ script file in the root directory of the project as shown above.
I can print variables or text from scripts, for example:
10 print "hello from my script"
and it will show up in the console view as below:
This is useful to ‘debugging’ scripts. To use a script in a (relative) sub-folder of the project, the path has to start with a ‘\’ or ‘/’:
Using LinkServer
Another way is to run scripts from the LinkServer program, which is a command-line utility. Inside the installation there is a utility I can use in interactive mode:
binaries\redlinkserv --commandline
then load the script and run it:
load "script.scp" run
If the script has been edited outside, use the ‘new’ and ‘load’ again:
new load "script.scp" run
To exit the session:
exit
Another way is to run a gdb server session listening on a port:
linkserver gdbserver --redlink-telnet-port 12345 LPC845:LPC845
Note that here I have to provide a device name/board. Then I connect to the port with a telnet program (e.g. putty or nc) and use the commands as above.
If using PuTTY, enable the Terminal setting ‘Implicit CR in every LF’.
Unlock Script
Below is a script which tries to unlock a ‘bricked’ MCU: first it searches for an attached debug probe. Then, inside a loop, it tries connect and halt the device:
10 PRINT "*****************"
11 PRINT "* Unlock Script *"
12 PRINT "*****************"
20 PRINT "search probe ..."
30 PROBELIST
40 p% = PROBEFIRSTFOUND
50 PROBEOPENBYINDEX p%
100 PRINT "connnecting..."
110 w% = WIRESWDCONNECT p%
111 REM PRINT "w: "; w%
112 REM IF w% == 0 THEN GOTO 1000
200 i% = 0
210 REPEAT
230 w% = WIRESWDCONNECT p%
231 REM PRINT "w: "; ~w%
240 u% = SelectProbeCore p% 0
241 REM PRINT "u: "; u%
250 v% = CMINITAPDP this
251 REM PRINT "v: "; v%
260 h% = CMHALT this
261 REM PRINT "halt: "; h%
270 i% = i% + 1
280 UNTIL i% >= 10
300 PRINT "reached max retry: "; i%
310 END
1000 PRINT "failed"
1010 END
I have not found a good way to stop the script after it is able to halt to the target. To keep it simple, it just tries to halt the MCU until the max retry count is reached: change that value i% if you need it to run for a longer time.
Running the Unlock Script
While the script is running, it tries to halt the MCU. So during this time, I repower or reset the MCU many times, to give the script a chance to halt the MCU. Below is the output where it was able to halt it after 10 retries and received the DpID and APID:
***************** * Unlock Script * ***************** search probe ... Index = 1 Manufacturer = NXP Semiconductors Description = LPC11U3x CMSIS-DAP v1.0.7 Serial Number = 1103F02B VID:PID = 1FC9:0132 Path = \\?\hid#vid_1fc9&pid_0132&mi_00#7&35c6d4a8&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030} Probe Handle 1 Open trying to connnect to target... Error: Wire Ack Fault - target connected? Iteration 0 Error: Wire Ack Fault - target connected? Error: Wire not connected Error: Wire not connected Iteration 1 Error: Wire Ack Fault - target connected? Error: Wire not connected Error: Wire not connected Iteration 2 Error: Wire Ack Fault - target connected? Error: Wire not connected Error: Wire not connected Iteration 3 Error: Wire Ack Fault - target connected? Error: Wire not connected Error: Wire not connected Iteration 4 Error: Wire Ack Fault - target connected? Error: Wire not connected Error: Wire not connected Iteration 5 Error: Wire Ack Fault - target connected? Error: Wire not connected Error: Wire not connected Iteration 6 Error: Wire Ack Fault - target connected? Error: Wire not connected Error: Wire not connected Iteration 7 Error: Wire Ack Fault - target connected? Error: Wire not connected Error: Wire not connected Iteration 8 Error: Wire Ack Fault - target connected? Error: Wire not connected Error: Wire not connected Iteration 9 DpID = 0BC11477 APID = 0x04770031 reached max retry: 10
At this state showing DpID and APID (ID 0x04770031 is for an ARM Cortex-M0+), the MCU is halted and I can erase it or load a new firmware. Success! 🙂
Summary
I have now scripting capabilities available with the LinkServer (LPC-Link, MCU-Link and MCU-Link Pro) debug probes. This gives me an inexpensive hardware tool to recover MCUs in some cases, and you might find the scripting capabilities useful for your special debug needs too. The LinkServer scripting language is very BASIC (pun intended), gives you access to the debug hardware and low level stuff, and with some help of existing scripts and forum questions I got it up and running. As for the unlocking script, I have it running ‘semi-automatic’ in the ‘interactive’ mode which works fine. Ideally I would run the script from another script, and if I find out how to do this, I will update this article.
The recovery approach I’m using here is to run a script repeatedly to gain access to the device after reset or power-on. It seems to work fine at least with the cases I had. But for a higher speed it might be necessary to run the code on the debug probe itself. This is maybe something I will look at in the future.
Happy recovering 🙂
Why in the world did they choose BASIC syntax for the scripting language?! Well, I guess it is NXP after all…
LikeLike
My guess is that this comes from the code_red days. I wrote about the Red Suite (see https://mcuoneclipse.com/2013/04/20/red-suite-5-eclipse-juno-processor-expert-and-unlimited-frdm-kl25z/) and shortly after they were acquired by NXP. All the ‘RedLink’ or ‘RedLib’ stuff has originated from them, when the transformed into the LPCXpresso. I believe they selected for some reasons back that time BASIC as scripting language, and this is what has been used in the back since then I think, for more than 10 years.
LikeLike