Hardware/EIR/OpenOCD/Clocks
Contents
OpenOCD for AT91SAM7SE - Part 4
This is part 4 of our OpenOCD for AT91SAM7SE tutorial.
Configuring the Clocks
Using Tcl
Although OpenOCD configuration files may look like ordinary text files, they are not. Instead they are executables, or more precisely, Tcl scripts. OpenOCD includes a tiny Tcl interpreter named Jim Tcl. Using script languages instead of dumb configuration lines can make configurations smarter.
And actually we need this now. So far, OpenOCD reads our configuration file and immediately executes the commands line by line. To setup the PLL in order to get our CPU running at full speed, we want to access certain I/O registers. Naturally, we won't be able to do that until we have created and configured the TAP and the debug target via jtag newtap
and target create
, resp. No problem, as long as we keep the commands in our configuration file in the right order.
But what, if we press the reset button? The CPU will return to its slow clock and OpenOCD would have to re-read and re-execute the configuration again. That's just a simple example. Typically there are other events than reset, which we need to handle when debugging a target board.
OpenOCD provides a clever solution, which may be confusing, if you are not aware of it. The program starts in a so called configuration stage, where all configuration commands are executed immediately while reading them from the configuration file. When done, it switches to the so called run state. Be aware, that some commands are rejected during the configuration stage, while others are rejected in the run state. Of course, many commands may be executed in either state. Furthermore, Tcl commands to be executed during run state must be defined within Tcl procedures.
I know... some of you do not want to learn a new programming language just to get OpenOCD up and running. I'll promise you, you don't need to learn more than a few simple syntax rules, which are quite similar to other programming languages you are already familiar with. Frankly, I never liked Tcl, but that's between ourselves.
Accessing Memory Addresses
OpenOCD provides a few commands for reading from and writing to memory addresses. Obviously they are accepted in run mode only. As we learned, we have to create a Tcl procedure. But this is true for the configuration file only. As soon as OpenOCD finished reading that file (or files, as we will see later), it switches automatically into run state. To interactively execute commands in this state, we can use OpenOCD's Telnet interface. If not running already, start OpenOCD in one command line window. Then open another command line and enter
telnet localhost 4444
You will get the OpenOCD command prompt, ready to serve you. The most important command is memory write word, mww
followed by the memory address to write to, followed by the data to write. Let's try the internal SRAM of the AT91SAM7SE, which is located at memory address 0x200000:
mww 0x200000 0x12345678
In most cases this will immediately fail, because the CPU is running and occupying the memory bus. To stop the CPU, enter
halt
Now try the memory write command again. You can use the cursor keys on your keyboard to recall previously entered commands. To read from the same address, use the memory display word command, mdw
followed by the memory address to read from.
mdw 0x200000
It should display the same value that we wrote into this memory word.
OK, now get your data sheet and write the right values to the right registers to enable the right PLL to let the CPU run at the right speed. You got all informations required to do that.
Too hard? What's your problem? You don't know where to start reading? Believe me, there are chips on the market, which will require hours of hard work to get the clocks working. The AT91SAM7SE is one of the most convenient parts. Anyway, I'll give you a helping hand.
Setting up the PLL
Roughly following chapter 29.7 of the AT91SAM7SE data sheet, we will first enable the main oscillator by writing 0x701 into register CKGR_MOR. The startup time is set to 56 slow clock cycles.
mww 0xfffffc20 0x00000701
Next, we set up the PLL divider and multiplier to generate a frequency of 96MHz. We will set the divider to 14 and the multiplier to 72. Based on the 18.432MHz crystal mounted on the EIR, we will get 18.432MHz * (72 + 1) / 14 = 96.11MHz. The startup time should be 28 slow clock cycles. This setting is written into register CKGR_PLLR.
mww 0xfffffc2C 0x00481c0e
Now we select the PLL clock divided by 2 as our master clock in register PMC_MCKR.
mww 0xfffffc30 0x00000007
As soon as this has been done, our CPU is running at 48MHz.
Finally we set register MC_FMR to 1 wait state for read and 2 wait states for write access to the internal flash memory. This is not really required until we really access that memory area. But we do it here, because it depends on our clock settings.
mww 0xffffff60 0x00480100
Now we want to put this sequence into a Tcl procedure to be placed into our openocd.cfg configuration file. But wait. As explained in the data sheet, we have to wait at certain points until the clock has become stable. When entering commands manually, this is no issue, because the clocks will become stable before we are able to enter the next command. However, executing the same commands within a script may be too fast. One solution would be to use the mem2array command to read examine register contents. But is it worth the effort, just to get the target up and running a few milliseconds earlier? Probably not, so let's add a simple sleep command instead, followed by the number of milliseconds that OpenOCD should wait before executing the next command.
You may later come back to this piece and it may be hard to find out, what this code is actually doing. We could, for example, use Tcl variables for register names to make the code more readable. I prefer to use good comment lines:
# Initialize the EIR clocks. # # The board uses an 18.432MHz crystal. # Let the CPU run at 48MHz. # proc eir_init_clock {} { # Enable main oscillator. # # Start-up time of 7 x 8 slow clocks. # mww 0xfffffc20 0x00000701 ;# CKGR_MOR sleep 2 # Configure the PLL. # # 18.432MHz * (72 + 1) / 14 = 96MHz # # Divider 14 (0x0e) # Start-up time of 28 (0x1c) slow clocks # Multiplier 72 (0x48) # mww 0xfffffc2C 0x00481c0e ;# CKGR_PLLR sleep 1 # Select PLL clock and divide it by 2 # # 96MHz / 2 = 48MHz # mww 0xfffffc30 0x00000007 ;# PMC_MCKR sleep 1 # 1 wait for read, 2 waits for write # We have 48 master clocks in 1us mww 0xffffff60 0x00480100 ;# MC_FMR }
Note: People may report their bad experience with OpenOCD, and justifiably so. As you can see, # is used to mark a comment. However, using this on the same line after a command had been working in early releases, but stopped working at a certain version (which I cant remember right now), making many existing configurations fail, even those included in the official distribution. Adding a semicolon in front of the comment fixes this, but... There had been several other cases of incompatibilities and users tend to avoid updating OpenOCD or using OpenOCD at all. Luckily, this seems to have changed lately, at least upgrading from 0.5.0 to 0.6.0-rc1 was totally painless. In general, OpenOCD is a most reliable tool. I just wanted to explain, why a few developers are talking bad about it and try to avoid it like a plague. If you are one of them, give it one more try, it's worth the effort. Now, back to the topic.
Add the procedure eir_init_clock to your current openocd.cfg. If you still have the Telnet connection established, you can enter
shutdown
to terminate both, Telnet and OpenOCD.
You can test the Tcl procedure by simply entering its name in the Telnet session, but this won't make much sense until we have declared a reset event handler. With the current configuration, even resetting the board via OpenOCD won't work. This will be fixed in the next step.
Reset Configuration
JTAG specifies two reset signals, nSRST and nTRST. While the first one is used to reset the hardware (like pressing the reset button), the latter is used to reset the JTAG TAP. In fact, both are optional, because targets may offer alternative ways to reset the system and/or the TAP to a known state. The trouble is, that sometimes these signals are implemented in strange ways. For example, most targets allow JTAG communication while mSRST is held low, others don't. This makes it impossible for the debugger to stop the target before executing the first opcode. Many targets and JTAG adapters, including the AT91SAM7SE and the Turtelizer 2 board revision B, do not require or provide nTRST. As a result, we must add another configuration item to tell OpenOCD, what kind of reset mechanism is available. For the AT91SAM7SE we declare
reset_config srst_only
This will make the reset command available and we can finally implement the related handler.
Creating a Reset Handler
Sooner or later we will have to do more than just setting up clocks. Let's define a reset handler procedure, which in turn calls our eir_init_clock
procedure we defined above.
# Initialize the EIR board. # proc eir_init {} { eir_init_clock }
This routine will be registered as the reset-init handler of our target.
sam7se512.cpu configure -event reset-init { eir_init }
You may add the procedures eir_init_clock
and eir_init
as well as the configure
command to your openocd.cfg file in any order. It doesn't matter. Try to run OpenOCD with this new configuration, establish a Telnet session and enter
reset init
Your output should be similar to
JTAG tap: sam7se512.cpu tap/device found: 0x3f0f0f0f (mfg: 0x787, part: 0xf0f0, ver: 0x3) target state: halted target halted in ARM state due to debug-request, current mode: Supervisor cpsr: 0x600000d3 pc: 0x00000000 NOTE! DCC downloads have not been enabled, defaulting to slow memory writes. Type 'help dcc'. NOTE! Severe performance degradation without working memory enabled. NOTE! Severe performance degradation without fast memory access enabled. Type 'help fast'.
OpenOCD detected, that our current configuration may lead to slow downloads and prints some flashy notes. Let's ignore them for the time being.
Before moving to the next part, you may want to verify your current contents of openocd.cfg.
interface turtle ft2232_layout turtelizer2 ft2232_device_desc "Turtelizer JTAG/RS232 Adapter" adapter_khz 8 jtag newtap sam7se512 cpu -irlen 4 -expected-id 0x3f0f0f0f target create sam7se512.cpu arm7tdmi -chain-position sam7se512.cpu reset_config srst_only sam7se512.cpu configure -event reset-init { eir_init } # Initialize the EIR board. # proc eir_init {} { eir_init_clock } # Initialize the EIR clocks. # # The board uses an 18.432MHz crystal. # Let the CPU run at 48MHz. # proc eir_init_clock {} { # Enable main oscillator. # # Start-up time of 6 x 8 slow clocks. # mww 0xfffffc20 0x00000601 ;# CKGR_MOR sleep 2 # Configure the PLL. # # 18.432MHz * (72 + 1) / 14 = 96MHz # # Divider 14 (0x0e) # Start-up time of 28 (0x1c) slow clocks # Multiplier 72 (0x48) # mww 0xfffffc2C 0x00481c0e ;# CKGR_PLLR sleep 1 # Select PLL clock and divide it by 2 # # 96MHz / 2 = 48MHz # mww 0xfffffc30 0x00000007 ;# PMC_MCKR sleep 1 # 1 wait for read, 2 waits for write # We have 48 master clocks in 1us mww 0xffffff60 0x00480100 ;# MC_FMR }