Today, my son tried doing some programming on the KL25Z board, which I had been playing with yesterday (after reloading the MBED download software). I has been using the MBED online compiler, but he wanted to use gcc, using entirely code he wrote himself.
Over the weekend he had figured out how to do the clock startup and he wanted to test that, as well as the whole gnu tool chain for programming KL25 chips, as he plans to use a KL25 chip rather than an ATMega chip in the product he is designing.
The clock startup is a rather complicated state machine on the KL25 processors, which start out using a 32kHz internal clock, then need to be switched via two intermediate states to using the external 8MHz crystal with the phase-locked loop. The state diagram for legal transitions is given in Figure 24-16 of the KL25 Sub-Family Reference Manual:
Figure 24-16, the legal state transitions for the clock generator module.
We have to get from FEI (Frequency-locked-loop Engaged with Internal clock) to PEE (Phase-locked-loop Engaged with External Clock), pasing through FBE (Frequency-locked-loop Bypassed External) and PBE (Phase-locked-loop Bypassed External). There are some tests that need to be done to make sure that the external oscillator has stabilized before making the transitions.
He figured out how to do this startup with very little help from me (I’ve never seen such a complicated startup procedure)—basically we read the manual together and I explained the idea of the state diagram, but I did not read all the details about what was needed to enable the transitions or what registers needed to be set to what values to make the transitions.
He also spent a lot of time figuring out how the gnu linker scripts work, so that he could put things (like the initial stack pointer and PC values) in specific places, without having to write assembly language. I’ve never worked with linker scripts, so he was on his own for that!
Today when he tried downloading his first program (a simple color-change blinker that used a system tick interrupt for timing), it didn’t work. He faced the first problem of doing embedded programming—a bug results in no output. Figuring out what went wrong is very difficult when there is no response. He turned to me for help debugging—despite the fact that I have no knowledge of ARM assembly language nor gnu linker scripts.
Somewhat surprisingly, I was able to help him. Here are some things that I helped him with today:
- Linker symbols are addresses of variables, not constant integers. When you want the value of a linker symbol in a C program you need to use &symbol to get the address.
- To disassemble raw binary files, you can use objdump, but you need to give lots of extra options, to make up for the lack of internal information:
objdump -D -m arm -b binary --disassembler-options=force-thumb foo.bin
I’ve not used objdump in a long time, and never with a raw binary—we had to read the objdump manual to figure out what options were available. He spent some time disassembling both MBED binaries and the ones he generated with the gnu gcc compiler and linker, looking for important differences. He noted that the machine code generated by gcc with the default optimization setting was difficult to read, but when he turned on optimization it got cleaner and simpler, using the standard instructions for subroutine return, for example.
- Pointer arithmetic in C knows the size of the objects pointed to, so incrementing a uint32_t* pointer to the next word is done by ++, not by +=4. He had never used pointer arithmetic before, and had not realized that pointers and integers increment differently.
- Linker scripts do not do link-time expressions, but require constants. He had a computed location (the start of RAM on the chip, which is at 0x20000000 – (ram_size/4) ), but the linker script was not doing the computation. When he switched to using the constant 0x1ffff000 in the linker script, the initialization code he’d written for clearing the BSS region then had the correct start location to clear.
- I asked him if he had turned on clock gating for the GPIO module and the port module he was using. (I think that GPIO clocks can’t be turned off, but that the port module clocks can be, disabling the port to save power. GPIO uses the platform clock, but the ports use the bus clock.)
- In linker scripts, if a block has nothing in it, then the “.” pointer is not changed, even if the block should be in RAM rather than flash memory. His bss_start address was messed up when his data block was empty, because the “.” still pointed to the word after the end of the program. He fixed this by adding .=ram_start to the appropriate place in his linker script.
Of the things I helped him debug, about the only things I knew ahead of time were the pointer arithmetic and that linker symbols are addresses. He was doing a lot of stuff that I’ve never done (like using gcc’s
__attribute__ keyword to make symbols weak so that interrupt service routines could default to a null handler, but be overridden in the linker by defining a real interrupt service routine).
By the end of the day, he had written a blinky program using the System Tick Timer with his own startup code and interrupt vector table, written entirely in C, compiled and linked entirely with the gcc toolchain downloaded with the OpenSDA chip—and he got it all to work. This is about an order of magnitude or two harder than writing the same blinky program using the MBED software development kit and online compiler. He needed to read big chunks of the KL25 reference manual to figure everything out, and some parts we had to read together.
In fact, we’re still not sure whether there is any reason to enable the clocks to port A before doing the clock startup sequence, which the MBED code does. As far as we can tell from the manual, the port starts out with the pins defaulting to XTAL0 and EXTAL0 (the crystal), so it shouldn’t be necessary to turn on port A, but the manual is not really very clear on this point. [Update: 2013 Jluy 24 00:45. My son checked, you do not need to enable port A. The external oscillator starts up fine with port A left turned off, as we thought.]
I doubt that I could have done an all-C, all-gnu program any quicker than he did—and he now knows far more about ARM processors than I ever will. I was just pleased that I have enough general debugging experience to have been able to help him the few places he got stuck.
He plans to use the KL25Z board in building a prototype of the device he has been doing PC board layouts for all summer. He’s going to make a through-hole-part board for prototyping the electronics. The board will not include the processor, but will have lots of connectors . The Freedom KL25Z board is far too big to fit in the final case, but having a debugged development board and a through-hole-part PC board for the other will let him debug his design more easily than the all-SMD board that will be needed for the final product (he’s aiming for a 5cm×5cm final board size including the processor and all peripherals). When he does get the SMD board designed, he’ll be able to use the KL25Z board as a programmer for it, since there is a simple 10-pin connector on the KL25Z board that allows using the “OpenSDA” chip on the board to program any KL25 chip through the single-wire-debug interface. He’ll put a similar connector on the boards he designs, so that he can load firmware onto the boards (and so that hackers can load their own programs, once the open-source design is released).