Recently I attended a seminar organized by Freescale which was devoted to their Kinetis ARM microcontroller family. The participants were provided with the demo-board FRDM-KL05Z shown in the left image below. The microcontroller on this board is based on the Cortex-MO+ kernel, which is optimized for low-power applications. Since such applications are one of my main interests, I decided to give the Kinetis family a try and design a test application based on a microcontroller located outside of that board.
FRDM-KL05Z board | Cutting PCB | |
Working with any microcontroller starts with figuring out a way how to program it. The demo-board is equipped with a USB programmer/debugger which can also be used for programming external chips via the SWD interface. The external programmed device can be attached to the board via the J1 connector in the red circle (I soldered a 10-pin female socket there). In order the on-board microcontroller won't affect programming the external chips it has to be disconnected from the programmer. This can be achieved by simply cutting the PCB trace leading to pin 30, which is shown in the yellow circles above. Obviously, this modification is easily reversible and the original functionality of the demo-board can be completely restored, if needed. Therefore, the programmed microcontroller can be connected to the board via pins 2 (SWD_DIO), 4 (SWD_CLK), 10 (RST_TGTMCU), and GND (pins 3, 5). The on-board voltage regulator can be used for powering the external circuit provided it consumes less than several dozens mA. Boosting its output voltage from 3v up to 3.3v can be achieved by short-cutting the on-board diode D3 (optional procedure).
The on-board programmer thus connects to the programmed chip via a 4- or 5-wire cable. The length of my cable is about 16cm. The programmer is supported by many commercial IDEs, in particular by Keil's μVision. My choice of this IDE has several reasons. The main one is that system supports development on both C and assembly languages. The free version of this system has limitation of 32KB on the length of the compiled code. The chips I work with have exactly that much memory. Following a tradition I always use assembly language in the first design on a new for me microcontroller. Using assembly language allows to better understand the microcontroller architecture and get a right feeling on it. The Cortex-M0 architecture supports just 50 16-bit machine instructions and 6 32-bit ones. The on-board debugger is recognized as CMSIS-DAP Debugger (see Debug tab in Project Options). One should also select it in the Utilities tab as programmer. In order the programmer/debugger to appear in the μVision selection windows, it should be in advance connected to the computer, of course.
Besides of programming/debugging functions, the demo-board can work as a COM-UART converter during the debugging. This feature is very useful for printing various messages to the computer screen. To support all mentioned functions the board implements the OpenSDA protocol which is used for loading the corresponding application code into the auxiliary microcontroller on board. Loading applications is provided by the bootloader. Switching to the bootloader mode is accomplished by pressing and holding the Reset button prior plugging-in the USB cord. After this the board adapter will be seen by Windows as an external flash drive. For turning on the debugger mode one should copy the file CMSIS-DAP_OpenSDA.S19 from the OpenSDA Applications folder onto the flash drive. After reconnecting the USB cord the adapter is ready for debugging and remains in this mode after subsequent disconnects from the USB. Check the board User Guide for more details.
Let us now turn to the microcontroller MKL05Z32. It belongs to the entry level subfamily KL0 of the Kinetis family. Besides of the fine pitch QFN and 48LQFP packages it is also manufactured in easily solderable 32LQFP ones. The microcontroller can be clocked on up to 48 MHz frequencies and is equipped with a reach set of peripherals including 12-bit ADC and DAC, DMA, UART, I2C, SPI, and others. For our applications we are mostly interested in the low-power CPU modes. ARM requires implementation of at least 3 CPU modes: RUN, Sleep, and Deep Sleep. However, the manufactures usually add more CPU modes, particularly low-power ones. Our microcontroller has totally 10 CPU modes, out of which we are interested in 3 of them: VLPR (Very-Low-Power Run), VLPS (Very-Low-Power Stop), and LLS (Low-Leakage Stop). In the VLPS and LLS sleep modes the CPU consumes about 3.8 and 1.8 μA with running timer, respectively, and wakes up from sleep in 4 μsec. The left picture below shows a breakout PCB for the microcontroller, while the right one shows a test thermometer project. The temperature is displayed in °C.
Breakout PCB | Programming the chip | |
The thermometer project involves direct LCD driving, frequent MCU wake-ups, and communication with an I2C temperature sensor. The temperature conversion is performed every 3.6 seconds. The average power consumption depends on the displayed digits and varies between 3 μA and 9 μA. Following a classical approach I started firmware development with a Blink-A-LED project by using C programming modules from the example programs. The resulting compiled file length turns out to be 9.8 KB even with the compiler code optimization option on. After digging deeper into the code I realized that it can be significantly shortened. The shortening is obtained not just due to programming in assembly language, but mostly due to omitting many code segments which are unimportant or unrelated to my thermometer application. This resulted to the total code length of 848 bytes.
Schematic |
The project source code consists of 4 files: MLK05Z4.inc, startup.s, hardware.s, and main.s, and can be considered as an example of an assembly language project. The first of this files consists of compiler definitions of various CPU registers. It also includes several macros, which make the life of assembly language programmer much easier. Note that the corresponding .inc file from the μVision package can be used for projects on C and is not suitable for assembly language programming. The second file (startup.s) includes definition of the stack segment, the flash control area, the interrupt vectors and interrupt handlers. After powering-up the microcontroller loads the stack pointer with the value at address 0 and passes the control to the Reset Handler at address 4. The Reset Handler starts with disabling the Watchdog timer and then jumps to the main routine from file main.s. In the Cortex-M0 architecture the CPU always runs in the privileged mode, which provides a direct access to all resources from any code line. The third file (hardware.s) consists of functions for configuring various hardware modules. Finally, the application user code is located in file main.s.
Right after the reset the CPU is clocked by a default 32-KHz internal oscillator, whose frequency is boosted up to 21 MHz with FLL. For low-poser applications we need to clock it from another internal 4-MHz oscillator and without FLL. This can be achieved by tuning the MCG (Multipurpose Clock Generator) module, which is specific to Freescale. This module can work in one of 6 modes and must be turned from the default FEI (FLL Engaged Internal) mode to the more economic BLPI (Bypassed Low Power Internal) mode. However, such transition can only be done via an intermediate mode FBI (FLL Bypassed Internal). The BLPI more forces the internal voltage regulator to enter a power-save mode, in which it can provide power to the other modules only if the APB bus is clocked at 800 KHz or less. All these settings are performed in the MCG_setup() function.
After turning the MCU oscillator into a power-save mode one needs to define the CPU Run and Stop modes. This is done by the SMC (System Mode Controller) in function SMC_setup(). The function sets VLPR mode for Run and VLPS mode for Stop. One can also use a more economic sleep mode LSS as Stop. For this one should comment out the first two lines in function SMC_setup() and uncomment the subsequent 4 code lines. In either case CPU wake-up is provided by the LPTMR0 (Low-Power Timer). This is the only system timer capable to run in both mentioned sleep modes. Note that for waking-up from the LSS one can only use the LLWU (Low-Leakage Wakeup Unit), since NVIC is turned off in this mode. The required settings are done in the 4 commented out lines mentioned above. The average power consumption by using the LSS sleep mode in our application is about 2 to 3 μA less compared to the VLPS sleep mode. However, the in-circuit debugger cannot be used with the LSS sleep mode. It is important to remember that for putting the CPU into a deep sleep mode one must set bit SLEEPDEEP in SYS_CTRL register located in the ARM kernel.
Next step is I/O ports setup. To get an access to the port registers one should first enable ports clocking in the SIM (System Integration Module) which is also Freescale specific one. Remember, that the ARM architecture supports only read/write operations with memory. In order to change a bit in a memory located byte one should first read that byte into a CPU general-purpose register, then modify it there, and after that write this register back to memory. Clearly, if it not very convenient and is slow, in general. To improve the situation Freescale equipped its microcontrollers with the GPIO (General-Purpose Input/Output) and BME (Bit Manipulation Engine) modules along with a specific mapping of addresses for performing direct logic operations with memory. Those features are used in the above mentioned macros. Check the device data-sheet for details.
As it is mentioned above, CPU wakes up when the timer LPTMR0 counter reaches the value set in the CMR register. The timer then resets the counter and requests an interrupt. The CPU is waked-up by the NVIC/LLWU modules and executes the interrupt handler, which in our application is common to both mentioned modules. Note that only one of the modules NVIC/LLWU is active in a time, depending on the choosed sleep mode VLPS or LLS. The LLWU module activates only by entering the CPU the LLS mode and automatically deactivates upon exiting from this mode.
Special care in the code is done to minimize the stack load. As it is known, entering an interrupt starts with saving CPU registers R0-R3, R12, LR, PC, and xPSR in stack. Those registers are automatically restored by exiting from the ISR. In our application the interrupts occur every 60 msec, which the LCD refresh rate. The corresponding ISR just inverts the state of the I/O pins connected with the LCD. It would be nice to disable automatic saving and restoring of the mentioned registers to reduce the CPU active time. To establish this we utilize the Cortex-M0 feature named Sleep-On-Exit, which is activated by setting bit SLEEPONEXIT in the SYS_CTRL register. After 60 LCD refresh cycles (period 3.6 seconds) the code needs to leave the LCD interrupt handler and return to main() in order to request new temperature conversion. To do it we reset the SLEEPONEXIT bit. It results in exiting the ISR and restoring the above mentioned general-purpose CPU registers. The SLEEPDEEP and SLEEPONEXIT bits are set again by entering the sleep mode after reading the sensor data.
Finally, the I2C module setup is done in the I2C_setup() function. This function starts with enabling clock for the I2C module in SIM. Since the I2C module frequency must not exceed 1/20 of the APB bus frequency, this module is clocked at 40 KHz. This is, of course, too low but acceptable in our case of seldom and short I2C transactions. Communication with the temperature sensor is provided by functions Request_Conversion() and Read_Temp(). To convert the binary temperature value T into the BCD representation we assume for simplicity that 0°C < T < 69°C. For extracting the tens of temperature we multiply T by 10 and drop the LSB according to the equation T / 10 = (T·26) / 256. This equation is only valid for T < 69. Encoding the tens and units of the T value into the 7-segment codes is done by using two code tables.
Last modified:Mon, Jan 23, 2023.