Thursday, July 24, 2014

The first project – Initialization and Timing

When I start with the new board, I develop firmware in successive steps with each step represented as an MPLab-X project. The first project that I do is usually the “10-Basic”. The goal of this project is to establish basic MCU initialization, configure oscillator to set the speed of MCU (INIT module), and develop (or migrate from the previous release) timing service (TMR module) and basic “information” service to control on-board LED and/or external LED/buzzer (BLI Module).

The first step is to decide at what speed we would run the MCU. PIC24EP series of MCUs could be configured to run at a speed of up to 70 MHz. This is the instruction cycle speed, which usually designated as Fcy. The first impulse would be to configure it to run at maximum allowed speed – that is at Fcy = 70 MHz. However, Fcy plays critical role in configuring other timing parameters – the frequencies of stand-alone timers as well as frequencies of multiple peripherals including UART, I2C, and SPI speed. These frequencies are defined as fractions of Fcy and these fractions are most often the powers of 2. Taking these fractions of 70 MHz will result in very odd numbers. For example, if we set a divider (usually called “prescaler”) for a timer to, let’s say, 64, the timer update interval will be 1.09375 usec – not an impossible number to work with, but also not an easy one to perform precise time calculations.

Thus, my decision was to reduce the speed of MCU by about 9% to 64 MHz for the sake of simplifying various timing calculations. This also provides some safety margin as the higher the frequency of MCU operation the more stringent are the requirements to the operation temperature. At 64 MHz (or MIPS) PIC24EP can withstand temperatures up to about 115 degrees Celsius, while at 70 MIPS it is limited to 85 degrees.

The board is equipped with the 12 MHz crystal oscillator. However, PIC MCU provides a PLL frequency multiplier and a pre-scaler and post-scaler registers so it is quite easy to convert any crystal frequency to almost any Fcy subject to some limitations for intermediate frequencies. My selections for the values of these parameters are documented in the Init.c file. If you are interested in specifics od oscillator configuration, they are pretty well documented in the Family Reference Manual for respective MCU family available at www.microchip.com.

The board has 4-position DIP switch – the initialization routine (Init.c) configures associated pins and captures their values. As this pins are read and their values set only during initialization, so flipping them over after the board already powered up does not have any effect.

Besides configuring the oscillator and reading switches, Init.c performs some other initialization functions – configures MCU global control registers (“fuses” in PIC terminology), disables all HW modules (like I2C, SPI, etc.), disable all interrupts while enabling interrupt nesting, configures all analog-capable pins as digital. Initialization interface defined in Init.h and switches values are exposed through Switches.h.

It is critical for the flight controller to have access to precise timestamps to be able to calculate derivatives and integrals of the sensors’ values, as well as rather useful to have access to other timing features, such as well-defined delays (“sleeps”) etc. This functionality is provided by the TMR module. Timer module utilizes three HW timers – 16-bit Timer1 for low-precision functions and a pair of even-odd 16-bit timers to create one high-precision timer. The high-precision timer provides timestamps with the resolution of 1 usec, which is sufficient for the flight controller. TMRProfile.h file defines which pair of the available HW timers should be combined into a 32-bit high-precision timer.

Timer1 is configured to generate interrupt every 125 usec; the interrupt routine increments 32-bit counter. This counter of 125 usec intervals, which I call “ticks”, is used to implement “delays” and “alarms”. Let’s say, you have an operation that may take from 3 to 5 msec depending on values of some parameters and other conditions, that you perform in a loop. However, you would like to repeat this loop exactly every 10 msec, so, depending on the duration of calculation in the loop, at the end of the loop you need to implement a variable delay from 5 to 7 msec. This can be easily achieved with the “alarm” – at the beginning of each iteration of the loop you set alarm for 10 msec and then perform the required calculation, at the end of the calculation before ending the iteration you wait for the pre-set alarm – bingo! Each iteration of the loop will take exactly 10 msec!

Another very important feature provided by the Timer module is the callback after a certain period of time. TMR.h provides prototype for the callback function, which should be implemented by the user. TMRCallBackAfter function allows to register user’s callback function with the Timer module and specifies when it should be called. When the required interval passes, the callback function will be invoked on the interrupt routine asynchronously to the execution of the main code. As usual with the interrupt routines, callback function should not perform any lengthy operations as it will block main execution thread for the duration of the callback. Active callback request can be cancelled by calling TMRCallBackDiscard function. My current implementation allows just for one active callback at any given time; however it would be quite easy to change the code to allow multiple active callbacks – just did not see practical use for it thus far. BLI module, which we will discuss shortly, provides an example of the need for the callback functionality.

BLI Module (“blinker”) provides functions to control on-board LED and/or external LED/buzzer. The MCU pin, associated with the LED control, is defined in BLILocal.h – did not see a need to create a separate “profile” header for this simple definitionJ. Blinker module provide functions to directly control LED (and buzzer, if connected) – BLISignalON, BLISignalOFF, and BLISignalFlip.

When the flight controller performs some lengthy operation, like initialization or calibration of sensors, or waiting for “arming”, or waiting for GPS to obtain first fix, it would be nice to inform user that an operation is taking place by blinking the signal LED at certain frequency or in certain pattern. It is possible to achieve this with the functions providing direct control over the LED, but would be extremely cumbersome. This is where Timer callbacks come very handy!

Blinker module may set the LED control and then subscribe for a Timer callback. In the callback function, which will interrupt the main routine performing the lengthy operation for a very short time, Blinker may flip the LED and re-subscribe for the next callback. BLIAsyncStart function defines blinking pattern, subscribes for callback, and immediately returns so that the main thread may initiate some lengthy operation, during which the Blinker will happily blink requested pattern on its own. When the lengthy operation completes, blinking can be stopped by a call to BLIAsyncStop.

This worked fine for me for quite some time until I exhausted my fantasy coming up with distinctive blinking patterns. To address this problem, I added to the Blinker the Morse table and BLIAsyncMorse function, which accepts as a blinking pattern some character string and then blinks it asynchronously. Morse blinking can be terminated by the call to BLIAsyncStop.

These three modules – INIT, Timer, and Blinker – are the first I implement for any new board. Project 10-Basic test the functionality of these three modules and allows to build upon them in the further development.


In the next post we will look at the asynchronous implementation of the serial UART data sender.

No comments:

Post a Comment