Crossworks or, more accurately, CrossStudio for the Arm, running on a mac is probably one of the better development environments. It has its quirky side but, so far, I am really happy with it. Now might be a good time to look at how projects are organised, how the code gets onto the target and how it is started up…
Crossworks allows the user to work on projects which form part of solutions. Projects appear in the project explorer as if they were a bunch of folders. Actually, this is a representation of the information held in an XML file which describes the project. This file can be edited in a text editor if you must or changes made in the project explorer and/or configuration view are written back to it.
This is noteworthy for a couple of reasons.
First, the ‘folders’ in the project view don’t actually exist on your computer. If you create a folder for, say, documentation or library files, CrossStudio will put the files in the project folder itself and will not create a folder with the same name.
Second, the files seen in the explorer need not be in the project folder at all. Have a close look at the properties of these files and you will see some of them are stored elsewhere. these entries are, then, pointers to the actual files. An obvious example of this is the set of files found in the ‘system files’ folder. If you look at the properties of the startup file for example, you will find it actually lives in the Library folder under your home directory. This is very convenient for when you want to use a common file like the startup code. However, you must be aware that editing one of these files will affect every project that points to it and that may not be good. Almost certainly won’t be good in fact. If you want to maintain a copy of the shared file in the project folder that is different to the original, you can import the file by right-clicking its name in the project explorer. That will make a copy and disconnect you from the original.
Third, The project description carries only relative location information for the local files in the project. /that means that you could, for example, make a copy of the project folder, complete with its .hzp file and, when you open that, you can be sure you are looking at files in the copy not the original. Some other IDEs hold absolute file locations and you can find yourself merrily editing what you think is a copy but is actually the original in a different project.
Programs built for STM32 targets will normally all have three files in the ‘system files’ folder. These are:
In here is pretty much just a table of interrupt vector handlers. These are declare using the .weak keyword which means that if the same identifier is used elsewhere for a function, then that definition will replace the one named here. For example, you only need define a function called SysTick_Handler for the linker to make that the actual systick handler instead of the default one declared here.
Also in this file is a small code segment to set up the stack pointer and to call the function SystemInit(). This is node immediately following a restart, after the stack pointer has been given a value. Again, this is declared weak and a default, do-nothing SystemInit() exists in this file in case you don’t write one. Remember this. If you write a function called SystemInit(), it will get called before the processor gets to do almost anything else. There is, then, no need to call SystemInit() yourself – the startup code will call it for you. Equally, don’t create a function called SystemInit() if you don’t want it called early.
A confusing thing happens in this file. The presence of a preprocessor macro is tested and the compiler either generates a jump to the ‘proper’ reset handler or the system goes into a loop expecting the processor to be started by the JTAG interface. More on this below.
Both these features are described in a comment block at the top of this file.
This is the more traditional C startup code that you might have expected. In here the variables are initialised, the stacks set up and a jump is made to the application entry point. Generally, this will be the function called main(). If your main() function is allowed to exit, it will return here and go into an endless loop.
Startup from reset.
This is a frequently asked question on the Rowley site. Why will my application not start from a power-on or reset even though it runs just fine when started by the debugger? The answer, apparently, is that there is a conditional compilation section in the STM32_Startup.s file. the compiler looks for a preprocessor macro called STARTUP_FROM_RESET. The actual code looks like this:
_vectors: .word __stack_end__ #ifdef STARTUP_FROM_RESET .word reset_handler #else .word reset_wait #endif /* STARTUP_FROM_RESET */
The very first entry in the vector table is the address to jump to on a processor reset. If the STARTUP_FROM_RESET macro is defined, the location is the ‘proper’ handler, otherwise, the location points to a simple endless loop. The JTAG debugging interface can break the processor out of this loop and set it on its path by jumping directly to the entry point. Forgetting to set this macro is an annoyance. You will get your code working fine and then hit reset or disconnect the JTGAG only to find the processor apparently crashed. You can find out if this is why it is stuck relatively easily. Connect the JTAG debugger and power up or reset the processor. Assuming nothing seems to happen, turn to the IDE, connect to the debugging device and select Target | Attach Debugger. Once the debugging view comes up halt the processor and you will probably find yourself looking the the startup loop. If you are not then something else is killing your code. I find this very irritating and keep forgetting. Even when I remember, I seem not to reliably be able to start the program without the debugger. I suggest that any hardware you build has at least one LED on it that only lights when your code is running so that you get an immediate visual confirmation. The thing is that, since the debugger can always run your code, it always looks OK when you re-connect the debugger to see what went wrong.
Pretty well all the STM32, and general ARM systems you come across are likely to jave a JTAG connector on them somewhere. The standard ARM JTAG connector is a 20 pin 0.1″ pitch box header. This may be all well and good on larger projects but, since I intend to put a STM32 processor on a robot measuring only 50mm x 36mm, it is a bit big. As far as I can tell, the least number of pins needed for STM32 JTAG is 7:
- TMS – Test Mode State – use 100k pull-up to VCC.
- TDO – Test Data Out.
- TDI – Test Data In – use 100k pull-up to VCC.
- TCLK – Test CLocK – use 100k pull-up to VCC.
- RTCK – JTAG Return Test ClocK (optional)
- VCC – Positive Supply Voltage
- GND – Digital ground.
- RESET – /RSTIN — active low reset input of the target CPU.
The RTCK can be done without so long as the Adaptive Clocking option is turned off in the debugger properties of CrossStudio. It might be needed by other hardware. The VCC connection is there to allow the target to power the JTAG hardware an so should be optional. This brings the number of required pins down to 6. However, at least on the Olimex ARM-USB-OCD JTAG adaptor, this is not sufficient to allow it to work. Presumably because something inside uses the target VCC. Never mind, there are small 10 pin connectors that can be used.
A standard exists for 10 pin headers to implement this set of connections:
Luminary have a 20-pin to mini-10-pin adaptor:
It would not be hard to make up an adaptor and, once you have gone down the DIY proprietary route, it might as well be an 8-pin adaptor.