logo slogan

Refresh my Memory…

By Flora McDonald

September 2018

I think it’s fair to say that I have a lot of experience in designing, developing and testing embedded systems and for a large chunk of my working life I have been lucky enough to use Lauterbach debuggers. For the older reader, please insert a fondly remembered [In-Circuit Emulator | 8bit processor]  [story | memory] here. During this time, I’ve picked up a few tricks and I would like to share some of those with you now.

 

Using your Lauterbach debugger it is possible to read and write target memory. The User interface has a nice feature for dumping memory and a double-click lets you enter a new value but I’m talking about setting values in the start-up process. Often, after you’ve selected the target processor and put the system in “Up” mode, you need to set some peripherals or memory interfaces, or clocks, or re-map some I/O pins, or you get the idea. The Data.Set command lets you do this. Here’s a few of examples:

 


Each example has a target address as the first argument: the first 4 use a memory access class (Supervisor, Data) more information about this can be found in the target guide; the final example uses a built-in function to return the address range of a variable.

 

  1. 1 Write the value 0x0000540 as a 32bit value (%Long) to address 0x40048004 – probably a memory mapped peripheral control register.

     

  2. 2 Write an 8bit binary value (x represents don’t care bits).

     

  3. 3 Write a zero terminated string to start at address 0x2000FE00

     

  4. 4 Fill addresses 0x100 to 0x1FF with a 32bit value (0x12345678)

     

  5. 5 Initialise an array. The symbol will be taken from the symbol table loaded into the debugger.

 

This kind of thing can be very useful if you need to mimic the actions of a boot-loader that hasn’t been completed yet to set the hardware up in order to be able to debug the application code.

 

Values can also be read from memory and a set of functions exist to do this: Data.Byte(), Data.Word() and Data.Long(), reading 8, 16 and 32bits respectively. These can be combined with Data.Set which we spoke about earlier. Here are some examples:

 

Print Data.Byte(SD:0x2000FE08)

&var=Data.Long(D:0x1000FE00)

Data.Set SD:0x1008 %Long Data.Word(D:0x1000)|0x10

Data.Set SD:0x2000FE00 %Long Data.Long(SD:0x200F8)&~0xFF00

 

  1. 1 Print the 8bit value at location 0x2000FE08.

  2. 2 Assign the 32bit value at location 0x1000FE00 to a script variable.

  3. 3 Read the 16bit value at 0x1000, OR it with 0x10 and write the result back as a 32bit value to 0x1008.

4 Read the 32bit value at 0x200F8, clear the second byte and write the result to 0x2000FE00

 

Another feature that I have used a lot is the Data.Assemble command. I can assemble a new function into memory to test, or I can change existing code if I spot a bug. I often get applications that need debugging or customising and I don’t always have the correct hardware to run them on – you’d be surprised just how many Cortex-M3 devices don’t run the same code! Sometimes I want to just be able to extract structures from a running RTOS in order to build a custom kernel awareness. I can use Data.Assemble <addr> nop to patch out low level hardware initialisation routines that aren’t needed but that prevent the RTOS from booting. Here’s some examples:

 

  1. 1 Two instructions are combined on a single line. The push will be assembled to address 0x20001000 and the pop will be assembled to the next available address which is dependent upon the target architecture.

     

  2. 2 A sequence can be split across lines using the line continuation character like this.

     

  3. 3 Fill a range with ‘nop’s.

 

Some of the newer Kinetis chips from NXP have a built-in hardware watchdog and you have only a very few clock cycles (sometimes as few as 256) after reset in which to disable it. A Data.Set/Data.Long() sequence would take too long. In this case the only option is to assemble some low-level code to target RAM, run it and reset the PC and stack pointers. Here’s an example:

 

 

Let’s suppose that I have assembled a new function in empty target memory which I think is a better replacement for an existing function (func2() ), I can use the Data.Reroute command to replace all instances of calls to func2() with my newly assembled function. I can also reroute functions within modules (normally object files or linker sections). Again, some nice examples:

 

  1.  

    1 Replace all calls to func2() within the range specified with a branch to address 0x20002000 (the newly assembled function).

  2.  

    2 In module hw_init, replace all calls to function gpio_init() with a nop.

  3.  

    3 In the .text section replace all calls to func1() with calls to func1a(). This can be useful if two algorithms

    are being compared side-by-side for performance or power consumption.

 

At its heart JTAG offers very few capabilities: read/write memory, read/write register, start/stop/step. It has to be simple so that it can be universal. However, by building on top of these simple building blocks some very powerful capabilities can be constructed. If nothing else, I hope I’ve got you thinking.

 


Let me know what you think?

Regards,

Flora. (email Flora at info@phaedsys.com )