Upload
christopher-kerr
View
44
Download
1
Tags:
Embed Size (px)
Citation preview
University of Technology, Sydney
Faculty of Engineering and Information Technology
DEVELOPMENT OF A MODERNISED SOFTWARE PLATFORM
FOR AN EDUCATIONAL ROBOT
Christopher James Kerr
Student Number: 10826598
Project Number: S13-123
Major: Electrical Engineering
Supervisor: Dr Peter McLean
A 12 Credit Point Project submitted in partial fulfilment of the requirements for
the Degree of Bachelor of Engineering
20 June 2014
Christopher Kerr
ii
STATEMENT OF ORIGINALITY
I, Christopher James Kerr, hereby declare that I am the sole author of this report.
I assert that:
All work shown in the body of this report is my own
All Appendices have been properly attributed to their original author
All theories, ideas, and results of others have been correctly referenced
All sources of assistance have been acknowledged by name in this report
Please be aware that some small samples of code have been reused from a
previous subject, 48434 Embedded Software. This code comprises an
insignificant portion of the finished project.
I must also acknowledge that this project is substantially based on the source
code of the original Maze Rover, developed and supplied by Dr Peter McLean.
Appendix B contains material created in collaboration with other students in
48580 Advanced Control, during the Autumn 2014 semester. They have been
acknowledged at the beginning of that chapter. However, I wish to clarify that the
related work presented in Chapter 7: “Control Implementation” is entirely mine.
Signed: __________________________________________
Christopher James Kerr
Date: 20 June 2014
Christopher Kerr
iii
ABSTRACT
In 48540 Signals and Systems, a robot platform, called the “Maze Rover”, is used
to teach signal and control theory concepts to Electrical Engineering students.
This robot is built upon obsolete technology, and a replacement is under
development.
The current Maze Rover platform is based upon the Freescale MC9S12A512, a 16-
bit fixed-point microcontroller. Whilst this chip is very capable, its current
application as the controller for the “Maze Rover” pushes its capabilities to the
limit, leaving no capacity for future expansion of features.
The new platform is built on a Freescale Kinetis 32-bit microcontroller, which
provides vastly improved performance and a number of additional capabilities.
This project demonstrates the reimplementation of the Maze Rover software on
this new platform, performed in C99 using the Freescale CodeWarrior toolchain.
A literature review of best practices in embedded development was conducted,
leading to the development of an Embedded Software Coding Standard. This
standard has been extensively documented and rigorously adhered to, to
demonstrate the development of high-reliability embedded software.
The core of the project is the development of a Hardware Abstraction Layer
(HAL) for this new platform. The HAL provides serial communications via UART
and SPI, data acquisition, analogue output, and EEPROM emulation. It
demonstrates the application of several interesting features of the new platform,
including the highly configurable hardware SPI implementation and use of the
Direct Memory Access peripheral.
The project also implements the original Maze Rover features, including
modulated wave synthesis, tone detection, and a phase locked loop. These
features have been tested to be functionally identical to the original platform.
Future development may extend this work by implementing a real-time
operating system. The new platform’s improved speed provides ample capacity
to run additional tasks, such as driving an LCD-based Human-Machine Interface
or performing network communications over TCP/IP.
Christopher Kerr
iv
ACKNOWLEDGEMENTS
I would like to acknowledge the support of Dr Peter McLean, whose excellent
teaching has kept me sane over the course of this degree. I must also acknowledge
that this project is substantially based on the source code of the original Maze
Rover, developed and supplied by Dr McLean, and I thank him for this resource.
My friends at UTS are owed thanks for putting up with me, far more than I
deserved! It’s been a long degree and I couldn’t have done it without you.
Finally, I express the utmost gratitude for my family, who have supported me in
more ways than I could possibly count.
ACKNOWLEDGEMENT OF PRIOR WORK
The Analog Interface board used in this project was designed during a previous
capstone project by Rosario Vambuca. Rosario provided the assembled board and
its design schematics for this project.
I must also acknowledge that this project is substantially based on the source
code of the original Maze Rover, developed and supplied by Dr Peter McLean.
Appendix B contains material created in collaboration with other students in
48580 Advanced Control, during the Autumn 2014 semester. They have been
acknowledged at the beginning of that chapter.
Christopher Kerr
v
TABLE OF CONTENTS
Statement of Originality ............................................................................................................... ii
Abstract ............................................................................................................................................ iii
Acknowledgements ....................................................................................................................... iv
List of figures ................................................................................................................................... vi
1 Introduction ............................................................................................................................ 1
2 Embedded C Code Quality ................................................................................................. 5
3 Project Setup ........................................................................................................................ 15
4 Hardware Abstraction Layer ......................................................................................... 25
5 High Level Functionality ................................................................................................. 43
6 Communication Lab Testing .......................................................................................... 49
7 Control Implementation .................................................................................................. 55
8 Conclusions........................................................................................................................... 63
References ...................................................................................................................................... 67
Table of Appendices ................................................................................................................... 69
Christopher Kerr
vi
LIST OF FIGURES
Figure 1: The ModCon Board ..................................................................................................... 1
Figure 2: Freescale Tower System (Freescale, 2012) ...................................................... 2
Figure 3: New Project Options 1 ............................................................................................ 15
Figure 4: Language and Build Tools Options .................................................................... 16
Figure 5: Extract from CodeWarrior Edition Comparison Table .............................. 17
Figure 6: Download Size Limit Error (Styger, 2012) ..................................................... 17
Figure 7: Librarian Model Setting for C99 ......................................................................... 18
Figure 8: Kinetis Clocking Overview (Freescale Semiconductor, 2011) ................ 22
Figure 9: MCG State Diagram .................................................................................................. 23
Figure 10: PUSHR Register Layout ....................................................................................... 28
Figure 11: Analog Interface Board Top Level Schematic (Vambuca, 2013) ......... 29
Figure 12: ModCon Analog Interface Breakout Panel ................................................... 30
Figure 13: AD7609 Serial Timing (Analog Devices, 2013, p. 10) .............................. 31
Figure 14: AD7609 Acquisition Timing (Analog Devices, 2013, p. 9) ..................... 33
Figure 15: AD5754R Serial and Load Timing ................................................................... 34
Figure 16: DAC Input Register Layout (Analog Devices, 2011, p. 26)..................... 34
Figure 17: DAC Input Loading (Analog Devices, 2011, p. 22) .................................... 35
Figure 18: Schematic Extract Showing Reset 0Ω Link (Freescale, 2011) .............. 36
Figure 19: Major and Minor Loops in DMA Requests (Freescale, 2011) ............... 37
Figure 20: Eeprom_Write Logic Flowchart ....................................................................... 41
Figure 21: Serial Protocol Packet Structure ...................................................................... 43
Figure 22: Coherent Demodulation Scheme ..................................................................... 47
Figure 23: SSB-Lower Time Domain .................................................................................... 49
Figure 24: SSB-Lower Frequency Domain ......................................................................... 49
Figure 25: SSB-Upper Time Domain .................................................................................... 50
Figure 26: SSB-Upper Frequency Domain ......................................................................... 50
Figure 27: SSB-HI Time Domain ............................................................................................ 51
Figure 28: SSB-HI Frequency Domain ................................................................................. 51
Figure 29: Voltage Controlled Oscillator Characteristic ............................................... 52
Figure 30: Phase-Locked Loop Operation.......................................................................... 53
Figure 31: Demodulated Message Signal Time Domain ............................................... 54
Figure 32: Demodulated Message Signal Frequency Domain .................................... 54
Figure 33: Quanser MAGLEV Apparatus ............................................................................ 55
Figure 34: Control Module Overview .................................................................................. 56
Figure 35: Non-Inverting Op-amp Buffer ........................................................................... 58
Figure 36 : Controller Architecture ...................................................................................... 59
Figure 37 : Feedforward Current Relationships.............................................................. 60
Figure 38 : Lab Response to Step Input .............................................................................. 61
Figure 39 : Lab Tracking of Triangle Wave Input ........................................................... 62
Figure 40 : Lab Rejection of External Disturbance ......................................................... 62
Christopher Kerr
vii
NOMENCLATURE
ADC Analog to Digital Converter
DAC Digital to Analog Converter
DMA Direct Memory Access
EEPROM A type of Non-Volatile Memory
Flash NAND Flash, a type of Non-Volatile Memory
ISR Interrupt Service Routine
LSB Least Significant Bit/Byte
Maze Rover An educational robotics platform used at UTS
MCG Master Clock Generator
ModCon Modular Controller microcontroller board
MSB Most Significant Bit/Byte
NVM Non-Volatile Memory
PLL Phase-Locked Loop
RAM, SRAM Random Access Memory. Volatile memory type.
RX Receive
SPI Serial Peripheral Interface
TCD Transmission Control Descriptor (for DMA)
TX Transmit
UART Universal Asynchronous Reciever/Transmitter
UTS University of Technology, Sydney
VCO Voltage Controlled Oscillator
Table 1: Project Nomenclature
Chapter 1: Introduction Christopher Kerr Background
1
1 INTRODUCTION
1.1 BACKGROUND
At the University of Technology, Sydney (UTS), engineering is taught with a
practice-based curriculum. Theoretical concepts learnt in lectures are reinforced
by application in the laboratory.
At present a robot platform, called the “Maze Rover”, is used in 48540 “Signals
and Systems” to teach signal theory concepts to Electrical Engineering students.
The core of this device is a microcontroller board called ModCon (MODular
CONtroller), a flexible platform deployed in laboratories for several subjects.
ModCon acts as the “brain” of the robot, controlling its functionality. Whilst in
other subjects ModCon is user-programmable, its use in the Maze Rover is strictly
as an embedded appliance running preloaded firmware.
1.2 PROJECT OVERVIEW
Figure 1: The ModCon Board
The microcontroller used on the ModCon (the large black microchip visible in
Figure 1) is obsolete, and a replacement microcontroller board (ModCon 2.0) has
been developed to modernise the platform. This has prompted the design of a
new revision of the Maze Rover robot (Maze Rover 2.0) built around ModCon 2.0.
This project involves the development of the control software for that
Chapter 1: Introduction Christopher Kerr Technical Outline
2
replacement robot. The purpose of this project is replicate the capabilities of the
current Maze Rover using a development prototype of Maze Rover 2.0. During
this process a methodology for development of high-quality embedded software
will be developed and explored.
1.3 TECHNICAL OUTLINE
The current ModCon platform is based upon an obsolete device, the Freescale
MC9S12A512, a 16-bit fixed-point microcontroller with a 25MHz clock, 512KB of
non-volatile Flash storage, 14KB of RAM and many peripherals and interfaces.
Whilst this chip is very capable, its current application as the controller for the
Maze Rover pushes its capabilities to the limit.
ModCon 2.0 is based on a modern device, the Freescale MK50DX256CLL10. A
member of the Kinetis K50 family, this device uses a 32-bit ARM Cortex-M4 core
clocked at up to 100MHz. The specific model chosen provides 256KB of Flash
storage and 128KB of RAM.
At the commencement of this project, the final ModCon 2.0 hardware was not
available, so development was performed using a Freescale Kinetis
TWR-K70F120M development board. This development board is designed to
integrate into the Freescale Tower System (shown in Figure 2), which provides a
modular system for development of embedded projects. In this project, an Analog
Interface Board developed by a previous student is used as a tower peripheral.
Figure 2: Freescale Tower System (Freescale Semiconductor, 2012)
Chapter 1: Introduction Christopher Kerr Project Goals
3
The TWR-K70F120M module is designed around the high-end Freescale
MK70FN1M0VMJ12. This model provides a number of additional capabilities
when compared to K50 family devices, including a hardware floating-point unit.
For the purposes of developing software for Maze Rover 2.0 the use of these
features has been restricted. Where the development of this project diverges
from the limitations imposed by a K50 family device, a note has been made to aid
future development.
1.4 PROJECT GOALS
The goal of this project is to completely re-implement the functionality of the
existing Maze Rover firmware on a prototype of the ModCon 2.0 platform. Major
functionality units of the Maze Rover firmware (as deployed in Signals and
Systems) to be re-implemented include:
Hardware Abstraction Layer
o SPI and UART interface drivers
o Analogue Inputs and Outputs
o Periodic timers
o Low level functions such as clock generation
Serial Communications Protocol
Discrete Fourier Transform
Phase Locked Loop, including
o Numerically controlled oscillator
Generation of modulated waveforms
Demodulation of acquired waveforms
It also features a Model Reference Adaptive Motor Controller. This advanced
controller is applied transparently, such that the complex transfer function of the
Maze Rover’s motor appears to be a simple first order system when measured
without an external controller. This allows for a relatively simple controller to be
developed by students in “Signals and Systems” using the first order model
developed from the observed system.
As finalised Maze Rover 2.0 hardware was not available during the development
of this project, an alternative control task was implemented on this platform
using Quanser MAGLEV apparatus, demonstrating the viability of ModCon 2.0 for
performing complex control tasks.
Chapter 1: Introduction Christopher Kerr Document Outline
4
1.5 DOCUMENT OUTLINE
Chapter 2: Literature review – Embedded C Code Quality
An overview of research conducted prior to commencing software development
for this project, including a discussion of code quality and its applicability in an
embedded software project.
Chapter 3: Project Setup
Getting started with the Freescale Codewarrior toolchain and understanding the
fundamentals of the Freescale Kinetis architecture.
Chapter 4: Hardware Abstraction Layer
Understanding the hardware of the Maze Rover 2.0 platform, and implementing
drivers for its UART, Periodic Timers and Analog Input/Output systems.
Chapter 5: High Level Functionality
Design and implementation of the Maze Rover 2.0’s application-specific software,
including the ModCon serial protocol and the Signals and Systems
communication functions.
Chapter 6: Communication Lab Testing
Verification of the Maze Rover 2.0’s ability to replicate the communications
functions of the original Maze Rover.
Chapter 7: Control Implementation
Implementation details and test results for a control system implemented using
the prototype Maze Rover 2.0 platform.
Chapter 8: Conclusions
A discussion of the final status of the project. A subsection on further work gives
an outline of the work required to use this project’s results in the Signals and
Systems laboratory, and suggestions for potential future refinements.
Chapter 2: Embedded C Code Quality Christopher Kerr Defining Quality Code
5
2 EMBEDDED C CODE QUALITY
2.1 DEFINING QUALITY CODE
For the purposes of this project, we shall consider quality code to be:
Stable: The software should have no known bugs, and all necessary
precautions must be taken to avoid introducing common errors.
Maintainable: The intent of code must be clear to the reader, and the
code’s purpose in the larger system must be thoroughly documented.
Portable: The code should not make unnecessary assumptions about
the underlying architecture or compiler, to permit porting between systems.
2.2 THE NEED FOR QUALITY CODE IN EMBEDDED SYSTEMS
The embedded environment is fundamentally different from the personal
workstation environment with which most programmers are familiar. The
operation of embedded systems is largely autonomous and unsupervised. In a
workstation environment some kinds of failure are annoying, but acceptable –
programs become unresponsive or crash, but the user is there to restart them.
This is not typically the case for an embedded system, where the user has little
awareness or control over the internal state of the system.
Furthermore, many embedded systems control safety-critical processes, such as
automobiles and industrial automatons. In this environment, unreliable software
can result in injury or death. Unfortunately, this is not hypothetical – in the 1980’s
at least three deaths were inflicted by Therac-25 radiation therapy machines,
which suffered from a race condition in a safety interlock. During the first Gulf
War, 28 US soldiers were killed when a Patriot Missile system failed to destroy a
SCUD missile because of a poorly designed timekeeping system which compared
incompatible data formats. More recently, it has been argued that some of the
“unintended acceleration” issues affecting the Toyota Camry may have been
caused by faulty software and inadequately designed fail-safes.
Chapter 2: Embedded C Code Quality Christopher Kerr The C Programming Language
6
2.3 THE C PROGRAMMING LANGUAGE
The C programming language is a popular choice for embedded software
development. Firstly, and most prosaically, it is often the only high-level language
with a supported toolchain for the target microcontroller architecture.
Conversely, few (if any) architectures support another high-level language but do
not support C. This makes C the de facto choice when code portability is required
or desirable. Secondly, C provides an appropriately minimalist level of
abstraction. The higher level languages currently popular on desktop computers,
such as Java and Python, do not provide good support for fast hardware I/O
operations, whereas C's arrays, structs, and bitfields translate neatly to low-level
manipulation of hardware.
Unfortunately, as a language, the C standard tends towards permissiveness. It is
certainly possible to write code which will compile as valid C despite being
difficult to understand, perverse, or not correctly reflecting the intentions of the
programmer. For instance, C has a system of type promotions which, ultimately,
permits a floating point number to be assigned to a boolean variable.
Furthermore, many edge cases in the C standard are “implementation defined”,
resulting in unpredictable program operation across compilers and
architectures.
Other sections of the language are well defined but prone to programmer error.
A misplaced semicolon after the declaration of a for loop or the test in an if
statement can completely alter the intended logical flow without causing
compilation errors. It is very easy to mistype the assignment operator = in place
of the comparison operator == (or vice versa) and in most cases these
substitutions still produce valid C. The operator precedence rules in C are
particularly notorious for their illogical design – the bitwise operators | and &
have lower rank than the comparison operator ==. This results in errors when
evaluating simple expressions such as:
if (x & y == 0)
In this example, most readers would assume the intended logic to be:
if ((x & y) == 0)
Chapter 2: Embedded C Code Quality Christopher Kerr The C Programming Language
7
However, C’s operator precedence rules have this result:
if (x & (y == 0))
Finally, we should consider that the same feature that originally made C
attractive, its minimal abstraction, can also be a hazard at runtime. C assumes that
programmers know what they are doing, and does not protect against run time
errors such as division by zero or dereferencing of invalid pointers.
Given these limitations of C, it is clear that the language must be applied carefully
when used for embedded systems. Used without restriction, C has features which
can negatively impact the stability, maintainability, and portability of code,
resulting in poor code quality.
A subtle but important consideration is the version of the C language used.
Modern compilers support two main options – ANSI C (commonly known as C89,
and published as ISO/IEC 9899:1990) and C99 (ISO/IEC 9899:1999). C99 was
recently withdrawn by the International Standards Organisation in favour of C11,
a new standard ratified in 2011, but compiler support for this standard is
essentially non-existent at this time.
Until relatively recently it was common to advise against the use of C99 (as of
2004, no commercial compiler supported C99), but it is now well supported by
most C compilers. C99 brings valuable features to the C language, such as:
The ability to mix declarations and code, i.e. to declare a variable at any
point in a function,
The first expression in a for loop may be a declaration, as in C++,
The inline keyword, which hints to the compiler that a function should be
included inline rather than called
Support for a Boolean type,
C++ style // one line comments,
More flexible initialisation of arrays and structs.
The utility of these features is seen as sufficient justification for the use of C99.
Chapter 2: Embedded C Code Quality Christopher Kerr Best Practices in Embedded Development
8
2.4 BEST PRACTICES IN EMBEDDED DEVELOPMENT
2.4.1 LITERATURE REVIEW
In order to develop a suitable coding standard for this project, I conducted a
review of the existing literature on embedded development using the C
programming language.
The following titles should be considered representative rather than exhaustive.
Many alternative coding standards exist.
2.4.2 MISRA-C:2004 (MIRA LIMITED, 2004)
“MISRA-C:2004 – Guidelines for the use of the C language in critical systems” is
primarily interested in addressing the reliability of embedded systems. Published
by the Motor Industry Software Reliability Association for application in the
automotive industry, MISRA-C is a highly restrictive coding standard which
defines a “safe” subset of the C programming language.
MISRA-C recognises the utility of the C programming language, but seeks to
eliminate the insecurities inherent to the language by defining a rule set
amenable to enforcement by static analysis tools.
Rules in MISRA-C are classified as either “Required” or “Advisory”. In order to
claim to be compliant with MISRA-C, all required rules must be followed.
Advisory rules are considered to be less important – it is not necessary to follow
any advisory rule, but it is strongly recommended that they be considered.
Required rules largely deal with eliminating the use of language features whose
behaviour (under C89) is unspecified, undefined, or implementation defined. Use
of such features can result in unintended behaviour, especially when porting
between compilers or architectures. Note that several required rules also
explicitly ban the use of C99 features – these rules are known to be retracted in
MISRA-C:2012, but no copy of that document could be located for review.
Further rules aim to restrict cases where C is considered to be overly permissive.
For instance, it is permissible in C for the same identifier to be used for both a
typedef name and a variable name. This is potentially confusing, so reuse of a
typedef name is banned by Rule 5.3.
Chapter 2: Embedded C Code Quality Christopher Kerr Best Practices in Embedded Development
9
Advisory rules are typically stricter cases of required rules. For instance, where
Rule 5.3 (required) bans the reuse of typedef names, and Rule 5.2 (required) bans
naming reusing a variable name such that a variable in the current scope hides a
variable in outer scope, Advisory Rule 5.7 issues a blanket ban on reuse of any
identifier regardless of namespace or scope.
The vast majority of the recommendations made by MISRA-C:2004 are sensible
and practical, but some specific Rules are considered to be too strict for
practicality in a typical embedded system. For example, Rule 14.7 requires that
functions have a single point of return. Such a rule can significantly complicate
the design of functions which should return early in case of error. Rule 17.1
forbids the use of pointer arithmetic outside of array addressing, a restriction
which is unhelpful when addressing hardware registers known to be at a
particular offset from a peripheral base address.
Portability is addressed by MISRA-C in an indirect but conclusive manner – by
banning the use of all non-standard language extensions and eliminating all
aspects of the language which are poorly defined, portability is guaranteed.
However, considering the thoroughly considered nature of the MISRA-C:2004
rule set, it shall be considered as a definitive reference – any explicit contradiction
of MISRA-C rules shall be documented in the developed coding standard.
2.4.3 BARR GROUP EMBEDDED C CODING STANDARD (BARR, 2013)
The Barr Group standard may be considered as a companion text to
MISRA-C:2004. Where the two documents overlap they are largely compatible –
the only significant difference is the adoption of C99 and its features. However,
the Barr Group standard moves beyond examining language features to concern
itself with style, and thus concerns itself largely with code maintainability.
The author’s hypothesis is that many bugs are introduced by maintenance
programmers, and that these bugs may be reduced by “the disciplined use of
consistent commenting and stylistic practices”. Style is a highly personal aspect
of coding, but I acknowledge that code does not belong to the original author, but
to those future programmers who will maintain it. As such, it follows that there
is value in a strictly codified style to maximise consistency.
Chapter 2: Embedded C Code Quality Christopher Kerr Best Practices in Embedded Development
10
The Barr Group standard is exhaustive in its codification of style. It incorporates
all the usual features of a style guide – naming conventions, rules for the
placement of braces, guidelines for comments and so on – but goes much further,
rigorously defining the acceptable use of whitespace.
This document is much easier to follow than MISRA-C, taking a more colloquial
style. It uses a clear and logical structure, with chapters based on common
language features (Procedures, Variables, Expressions, and so on). It thus serves
as a useful model for this project’s coding standard. This is permitted by the legal
notices of the document, requiring only the inclusion of a particular paragraph
from that section.
I chose to adapt this standard, rather than adopting it wholesale, for three
reasons: firstly, much of the document is concerned with style, and style is highly
personal. I would prefer to enforce my own style, and thus will codify it using this
document as a template. Secondly, there are points of significant disagreement
between this document and the Embedded Software Style Guide discussed in
section 2.4.5, and I am aware that many of my own conventions were strongly
influenced by that document. Adaptation has permitted the reconciliation and
incorporation of useful material from the Embedded Software Style Guide.
Finally, I believe that rewriting the standard has provided a deep familiarity with
the details of the result, superior to that gained by simply examining a document
prepared by another person.
2.4.4 THE ART OF DESIGNING EMBEDDED SYSTEMS (GANSSLE, 2008)
The Art of Designing Embedded Systems concerns itself with the entire process
of embedded software development. It is largely unsuccessful – it covers a wide
variety of topics in scattershot fashion, with topics ranging from debouncing to
team management philosophies. However, it does contain a thorough discussion
of quality code that informed this project, and Appendix A presents a Firmware
Standard.
By comparison to the previous two documents, this standard is dreadful –
imprecise, incomplete, and laden with bizarrely specific advice. Much of it is
copied verbatim from the discussion of code quality in Chapter 3. Jack Ganssle is
Chapter 2: Embedded C Code Quality Christopher Kerr Best Practices in Embedded Development
11
a highly respected embedded software engineer. The high quality of his published
articles and essays led me to review this text, but in review it is clear that this
book is largely assembled from summaries of Ganssle’s previous writing. The
longer form is used here merely to cover more topics rather than discuss topics
in greater depth.
The value in The Art of Designing Embedded Systems is not in its coding standard,
but in its discussions of code quality and particularly encapsulation. In this
context, encapsulation is taken to be the pattern of design which avoids global
variables by preferring C++-style Get and Set functions in the public interface of
a module. This pattern is useful, and not idiomatic in C.
The Art of Designing Embedded Systems is also notable for being the text that
originally drew my attention to the very useful MISRA-C standard reviewed in
section 2.4.2.
2.4.5 48434 EMBEDDED SOFTWARE: SOFTWARE STYLE GUIDE (MCLEAN, 2013)
Peter McLean’s Software Style Guide is included in this review for two reasons.
Firstly, it was my first exposure to a formalised coding standard, and has thus
influenced some of my most fundamental assumptions about coding style.
Secondly, it loosely describes the style used in the existing Maze Rover codebase.
The Software Style Guide was not written until several years after the Maze Rover
firmware, but the common ancestry of their respective styles is clearly apparent.
This style guide is concerned almost entirely with consistency, in the interest of
maintainability. It is less comprehensive than the Barr Group standard, giving
more general guidelines than specific rules. The Software Style Guide is a more
human document, giving guidance as to what a programmer “should” do rather
than forbidding the use of the incorrect version. This is because it is designed for
enforcement by a human reader, rather than a software static analysis tool.
From this document, I have adopted many of the naming convention and code
structure rules, and much of its advice on coding best practices such as scope
limitation (data hiding) and const correctness.
Chapter 2: Embedded C Code Quality Christopher Kerr Best Practices in Embedded Development
12
One notable conflict relates to single-line conditional or loop constructs, such as:
if (y < z)
x = y;
The Software Style guide requires the format shown above, without braces, but
with the following line indented as if it were wrapped in braces. The Barr Group
argues against this style, on the basis that single-line conditional constructs are
often expanded into multi-line conditional blocks. This introduces the potential
for error – the braces may not be added, and the indenting may trick the reading
into believe the braces are present. Instead, they require the following style, in
which even single lines are wrapped with braces:
if (y < z)
x = y;
This has the potential to prevent error, but adds two lines of mostly empty space
to every such construct. It does however have a beneficial effect on
documentation, as it removes the pressure to force a comment to fit in the space
available to the right of the single line.
In the original draft of my coding standard, I had preferred the Software Style
Guide approach, as I believe the more concise form is easier to read. However,
during the development of this project, I became aware of a bug discovered in
Apple Inc.’s SSL implementation, which affected a range of platforms, including
arguably embedded devices such as their iPhone mobile phones.
if ((err = SSLHashSHA1.update(&hashCtx, &serverRandom)) != 0)
goto fail;
if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0)
goto fail;
goto fail; // << duplicated line
if ((err = SSLHashSHA1.final(&hashCtx, &hashOut)) != 0)
goto fail;
The bug consists of a single duplicated line, which thanks to the omitted braces is
unconditionally executed unless the preceding test has failed. A goto sends
execution directly to a label named “fail”, bypassing several tests. Worse, the “fail”
label leads to logic which does not always signal failure - it is simply the function
Chapter 2: Embedded C Code Quality Christopher Kerr The Maze Rover Embedded C Coding Standard
13
return, which returns the error code in the err variable. As no error has yet
occurred, this causes the function to prematurely return without error.
Reading about this error (“GotoFail”), and a similar error involving a case
statement found in the OpenSSL codebase (“Heartbleed”) has changed my
position on the inclusion of such braces, and the final revision of my coding
standard requires them unconditionally.
2.5 THE MAZE ROVER EMBEDDED C CODING STANDARD
The resulting document can be reviewed in Appendix A. It contains discussions
of the rationale behind interesting, complex, or unintuitive rules, and has been
footnoted with justifications for diverging from MISRA-C:2004 and the Barr
Group coding standard.
Chapter 3: Project Setup Christopher Kerr Freescale CodeWarrior
15
3 PROJECT SETUP
3.1 FREESCALE CODEWARRIOR
Freescale CodeWarrior Development Studio is an Integrated Development
Environment (IDE) for Freescale microcontroller products. The latest major
version, CodeWarrior 10, takes the form of modular plugins running in the
popular open-source Eclipse IDE. The CodeWarrior IDE provides a complete
environment for development of Kinetis applications; including wizard-driven
project creation, a modern code editor, a Flash programmer, and a well-featured
debugger.
Development for this project was conducted using CodeWarrior 10.4 (later 10.5)
Special Edition, which is available at no cost but imposes a code size limit of
128KB for Kinetis K-series devices. This limit was not significant for this project
– the final optimised executable is well under 20KB, and this includes known
inefficiencies which were not addressed because they did not affect run-time
performance.
3.1.1 STARTING A CODEWARRIOR PROJECT
Starting a new project in CodeWarrior (File>New>Bareboard Project) firsts
prompts for a name and location, then for the target processor (MK70FN1M0 for
the TWR-K70F120M development board), then the programmer/debugger
connection to be used. The development board integrates a P&E USB Multilink
debugger. The next step gives our first meaningful choices – Figure 3 shows the
available options for Floating Point and I/O Support.
Figure 3: New Project Options 1
Chapter 3: Project Setup Christopher Kerr Freescale CodeWarrior
16
The Kinetis K70 used on my development board includes a floating point unit,
but for comparability with the target K50 architecture I used software floating
point emulation in this project.
The I/O support option specifies which version of the Freescale Embedded
Warrior Library is linked into the project. Freescale supply versions of stdio.h
which include basic hardware drivers, permitting easy usage of printf() for
debugging using the UART or Debugger Console. The third option, “No I/O”, does
not link any such drivers. Including stdio.h introduces significant code
complexity and size to the project, and I do not consider it appropriate for an
embedded environment. As such, I did not make use of this feature and selected
the “No I/O” option.
3.1.2 LANGUAGE AND COMPILER – EXPLORING THE USE OF C++
There are two other options in this step of the wizard – Figure 4 shows the
Language and Built Tools options. The final project used the settings shown in
Figure 4 – the C language with the GCC-ARM toolchain – but this was not my
initially preferred configuration.
Figure 4: Language and Build Tools Options
My original project proposal revolved less around code quality and more around
applying modern programming paradigms to the embedded world. To this end, I
had proposed that I would undertake the project using C++ and thus explore the
applicability of Object Oriented Programming to embedded environment.
In the version that was current in July 2013, CodeWarrior 10.4, the Freescale
tools were the default. Thus my first attempt at creating the project used C++ with
Freescale’s own build tools, on the assumption that this would be the most
thoroughly supported configuration.
Chapter 3: Project Setup Christopher Kerr Freescale CodeWarrior
17
Unfortunately, this configuration is not supported in CodeWarrior Special
Edition. Attempting to compile the created project gives the error “C++ is not
supported by the current set of licences and is disabled”. Examining Figure 5 it
can be seen that only the Professional Edition of CodeWarrior supports the use
of C++.
Figure 5: Extract from CodeWarrior Edition Comparison Table
However, the project creation wizard specifically presents GCC as an option, and
GCC is an open-source C++ compiler. Recreating the project using C++ with GCC
initially appeared successful – the project would compile without error. However,
trying to launch the project in the debugger would result in an error similar to
that shown in Figure 6 – “Download size limit has been exceeded. Please check
your license”. This was clearly nonsensical, as the empty application compiles to
less than 6KB.
Figure 6: Download Size Limit Error (Styger, 2012)
Investigation ultimately lead me to a blog entry by Erich Stryger (2012) which
confirmed that the debugger is capable of detecting the use of C++, resulting in
the error message shown when no C++ permitting license is found. I confirmed
this hypothesis by attempting debugging using the time-limited CodeWarrior
Professional Evaluation version. The only way to use C++ with CodeWarrior is to
purchase a licence for the professional edition. This option was discussed with
Dr Peter McLean and deemed unworkable due to the costs – Freescale does not
offer an academic discount (recommending the use of the free Special Edition).
and a one person-year license for CodeWarrior Professional costs US$1,995.00.
Chapter 3: Project Setup Christopher Kerr Freescale CodeWarrior
18
Whilst attempting to work around this issue I had already begun to write code
using GCC conventions, thus ultimately resulting in my use of the GCC compiler
even after switching to the C language.
3.1.3 USING C99
As discussed in Chapter 2.3 (The C Programming Language), it is desirable to use
the latest supported version of ISO C. CodeWarrior, the Embedded Warrior
Library, and GCC support the use of C99, but it is not the default and no option is
given during project creation.
If I attempt to make use of a C99 construction on an unmodified project, errors
result – for instance, including stdbool.h and attempting to define a bool results
in the error “unknown type name ‘bool’” and the warning “EWL support for C99
is not enabled”. Examining stdbool.h shows this construction:
#if !_EWL_C99
#warning "EWL support for C99 is not enabled"
The obvious solution to this issue, and the one I used for most of the development,
is to simply redefine the _EWL_C99 symbol prior to the #include for any C99
headers, like so:
#if !(_EWL_C99)
#undef _EWL_C99
#define _EWL_C99 (1)
#endif
#include <stdbool.h>
However, late in development I learnt that there is a more correct method. In the
Project Properties -> C/C++ Build -> Settings pane, under the Tool Settings tab,
there is a folder in the navigation tree named “Librarian”. Changing the Model
setting from “ewl_noio” to “c9x_noio” results in the correct symbol definition,
thus compiling the included C99 headers in the correct mode.
Figure 7: Librarian Model Setting for C99
Chapter 3: Project Setup Christopher Kerr Using the Kinetis Platform
19
3.2 USING THE KINETIS PLATFORM
3.2.1 DEVELOPMENT ON FREESCALE KINETIS
Freescale offer two options for rapid development on the Kinetis platform.
Processor Expert
Freescale Processor Expert is a GUI-driven automatic code generation tool which
is tightly integrated with Freescale CodeWarrior. Processor Expert has a database
of all peripherals available on the target microprocessor. Users add “Embedded
Components” to their project. Components provide a modular object-oriented
approach to embedded development, with each Component encapsulating the
functionality of an on-chip peripheral, popular external peripheral (such as an
LCD display), or pure software algorithm.
The cost for this simplicity and modularity is reduced flexibility. Processor Expert
generates an event-driven application framework, and the user must adapt their
process to suit. As Processor Expert ultimately generates C code for compilation,
I also considered using Processor Expert to produce driver code for integration
into my own application. However, inspection of the generated code showed that
this was not feasible – the generated code was tightly coupled with the Processor
Expert environment, and would have required substantial rework to function
independently.
Bare-Metal Sample Code
Freescale provide a package of Bare-Metal sample code for the K70 platform.1
This package is interesting, because it contains a number of driver libraries for
K70 peripherals. However, no documentation (beyond the sample programs) is
supplied for these libraries, and I have been unable to locate a version-controlled
copy of this resource.
Ultimately I preferred to use these libraries as a reference source, alongside the
Kinetis Quick Reference User Guide (Freescale Semiconductor, 2012), to develop
my own drivers which meet my needs and conform to my own quality standards.
1 Download (Freescale login required) from https://www.freescale.com/webapp/Download?colCode=KINETIS_120MHZ_SC
Chapter 3: Project Setup Christopher Kerr Using the Kinetis Platform
20
3.2.2 INTERRUPTS
Kinetis microcontrollers are designed around an ARM Cortex-M4 CPU, which
provides very powerful and flexible interrupt management through its Nested
Vectored Interrupt Controller (NVIC). It has a number of interesting features,
including the ability to dynamically allocate interrupt priorities, and interrupt-
tail chaining, which permits the next pending interrupt to be rapidly entered
without performing a complete pop/push sequence on the stack.
The NVIC Vector Table
Unlike most other architectures, Interrupt Service Routines (ISRs) on ARM
Cortex-M based devices are not special. An ISR may be any function which takes
no parameters and has return type void.
On the MC9S12 microcontroller used by the original ModCon, interrupts are
defined using the non-ANSI keyword interrupt followed by the interrupt vector
number of the peripheral. This tells the compiler that the function is a special
interrupt service routine, and to place a call to this function in the corresponding
position of the vector table.
void interrupt 26 PeriodicTimer_ISR(void)
On a Kinetis microcontroller, the ISR function is not special. ISR’s are connected
to their respective vector by placing a function pointer in the vector table, which
is located at <projectDir>\Project_Settings\Startup_Code\kinetis_sysinit.c.
void (* const InterruptVector[])()
__attribute__ ((section(".vectortable"))) =
/* Processor exceptions */
(void(*)(void)) &_estack, // 0 Initial Stack Pointer
__thumb_startup, // 1 Initial Program Counter
/* and so on up to vector 15 */
/* Interrupts */
Analog_ADC_RxComplete_ISR, // 16 0 DMA Channel 0
Analog_DAC_TxComplete_ISR, // 17 1 DMA Channel 1
// and so on up to vector 121
The non-standard attribute __attribute__ ((section(".vectortable"))) directs
the linker to place this table at an address defined by the linker map. The
CodeWarrior-generated Kinetis boilerplate initialises the NVIC with this address.
Chapter 3: Project Setup Christopher Kerr Using the Kinetis Platform
21
Interrupt Priority and the BASEPRI Register
The NVIC provides the capacity to set interrupt priorities, thus determining the
order in which interrupts are handled if they queue during the execution of
another ISR.
There are 16 possible priority levels, with 0 the highest priority, and 15 the
lowest. CPU_SetIrqPriority() exposes this functionality, taking as parameters an
IRQ number and a priority from 0-15. Note that Kinetis interrupts have both a
vector number and an IRQ. The first peripheral interrupt vector is 16,
corresponding to an IRQ of 0. Other IRQs may be calculated in this way by
subtracting 16 from the vector number.
The NVIC provides the capacity to “mask” execution of interrupts below a certain
priority level whilst preserving execution of higher priorities. This feature is
enabled by writing to the BASEPRI register. When BASEPRI is equal to 0, all
interrupts are executed. When BASEPRI is non-zero, interrupts with priorities
lower than or equal to BASEPRI are masked. Thus, when BASEPRI is equal to 1,
only interrupts of the highest priority will execute. Conversely, when BASEPRI is
equal to 15, all interrupts except the lowest priority will execute.
Enabling and Disabling Interrupts
Interrupts are enabled by writing a 1 to the relevant bit in the corresponding
NVIC Interrupt Set Enabled Register (NVICISER), and disabled by writing a 1 in
the corresponding NVIC Interrupt Clear Enabled Register (NVICICER). Before
enabling an interrupt, the pending flag for that interrupt should be cleared. This
can be achieved by writing 1 to the relevant bit of the corresponding NVIC
Interrupt Clear Pending Register (NVICICPR).
This functionality is handled by CPU_EnableIRQ() and CPU_DisableIRQ().
Critical Sections
Critical section inline-functions are defined which simply enable and disable
interrupts, because it is known that interrupts are never otherwise disabled in
this project. A more refined approach could make use of interrupt priorities and
the BASEPRI mask, but care must be taken to truly disable interrupts when it is
required (an example is seen in section 4.5.3).
Chapter 3: Project Setup Christopher Kerr Using the Kinetis Platform
22
3.2.3 CLOCKING
Freescale Kinetis microcontrollers have an extremely complex clocking system,
an overview of which is shown in Figure 8 below.
Figure 8: Kinetis Clocking Overview (Freescale Semiconductor, 2011, p. 214)
The Master Clock Generator (MCG) module controls clock generation. The System
Integration Module (SIM) controls clock dividers and clock gating.
Main Clocks
Ultimately, there are four main output clocks, derived from MCGOUTCLK via the
dividers OUTDIV1 – OUTDIV4 respectively:
Core / System Clock: The ARM Cortex-M4 CPU core clock (max 150 MHz)
Bus Clock: The source of most peripheral clocks (max 75 MHz)
FlexBus Clock: Clock for the external FlexBus (max 50 MHz)
Flash Clock: Clock for the program flash memory (max 25 MHz)
An interface for setting these dividers is exposed by MCG_SetClockDividers().
Additionally, many peripherals can be clocked directly from the MCG – see the
outputs on the right hand side of Figure 8, which provide access to the output of
the Oscillators, PLLs, FLL, and the internal reference clocks.
Clock Gating
All module clocks are disabled after reset. Before any peripheral can be used, it is
necessary to enable the clock to its module by setting the appropriate bit in the
corresponding SCGCx register.
Chapter 3: Project Setup Christopher Kerr Using the Kinetis Platform
23
Generating MCGOUTCLK
After reset, MCGOUTCLK is equal to 20.97MHz, sourced from a Frequency Locked
Loop driven by the internal 32.768 kHz clock (FEI mode). The maximum 2
supported clock speed for the microprocessor on the TWR-K70F120M is
120MHz. To achieve this frequency, it is necessary to transition the MCG to source
MCGOUTCLK from a Phase Locked Loop (PLL) driven by an external 50 MHz
oscillator (PEE mode).
Figure 9: MCG State Diagram
It can be seen in Figure 9 than in order to transition from FEI mode to PEE mode,
it is necessary to pass through a minimum of two intermediate states.
To enter FLL Bypassed External (FBE) mode, the MCG is configured to source
MCGOUTCLK directly, from either an external clock source or an internal
oscillator driven by an external crystal. The FLL remains locked, but neither the
FLL nor PLL output is used. This mode is used to transition the MCG from an
internal clock source to an external one.
To transition to PLL Bypassed External (PBE) mode, we configure and lock the
PLL, but leave its output unused. This involves configuring the appropriate
dividers, and this functionality is handled by MCG_calculatePLLDividers(). The
MCGOUTCLK is still sourced directly from the external reference.
Finally, the MCG is configured to use the now-configured PLL as the source for
MCGOUTCLK, transitioning the system to full-speed PLL Engaged External mode.
2 150 MHz core clock is unofficially supported within a reduced range of operating temperatures, but the part used on the TWR-K70F120M is specified for use at 120 MHz.
Chapter 4: Hardware Abstraction Layer Christopher Kerr Periodic Timers
25
4 HARDWARE ABSTRACTION LAYER
4.1 PERIODIC TIMERS
The Freescale Kinetis platform provides the Periodic Interrupt Timer (PIT)
module for implementing periodic timers. There are 4 independent PIT channels
available on the MK70FN1M0, each of which has its own interrupt vector.
The PIT channels are clocked from the Bus Clock. The value given by the LDVALn
register is loaded as the start value, then decremented by 1 on each clock tick
until it reaches 0. At zero, an interrupt is asserted and the value is reloaded.
To use the module, it must first be enabled. This involves enabling the clock to
the module by writing a 1 to the relevant bit in the SIM_SCGC6 register, then
clearing the Module Disabled flag in the PIT_MCR register. This functionality is
handled by PTimer_Setup().
Configuring a timer channel is then a three step process. First, the timer period is
set using PTimer_SetPeriod(). This function takes as input a timer channel and a
value to write to the register PIT_LDVALn. To minimise inter-module coupling,
calculating the required value is left to the user. To generate interrupts at a
frequency Fint Hz:
𝐿𝐷𝑉𝐴𝐿 =𝐵𝑢𝑠 𝐶𝑙𝑜𝑐𝑘 (𝐻𝑧)
𝐹𝑖𝑛𝑡 (𝐻𝑧)
To perform this calculation at runtime, the current Bus Clock may be retrieved
using MCG_GetClocks().
Once the period is set, the PIT channel’s interrupt may optionally be enabled
using PTimer_InterruptSetup(). This step is not required if the PIT is being used
to trigger a DMA event.
Finally, the timer may be started with PTimer_Start(). A running timer may be
halted using PTimer_Halt(). Note that the downcounter is reset when the timer is
started, restarting the timer period. If the period is changed while the timer is
running, the new period will take effect in the next timer cycle.
Chapter 4: Hardware Abstraction Layer Christopher Kerr Serial Communications Interface
26
4.2 SERIAL COMMUNICATIONS INTERFACE
4.2.1 UNIVERSAL ASYNCHRONOUS RECEIVER/TRANSMITTER (UART)
The UART peripheral is used to provide communications with a PC. UART2 on the
TWR-K70F120M is connected to the USB debugging interface, which enumerates
on the PC side as a USB Communications Device Class (CDC) device. This behaves
like a COM port in essentially all respects.
Setup of the UART peripheral is performed in UART_Init(). This driver exposes
only one parameter – the target baud rate. All other configurable settings remain
at the default configuration: 8 data bits, 1 stop bit, and no parity.
Baud Rate Calculation
In order to successfully communicate between a pair of asynchronous
receiver/transmitters, the baud clock on each device must match to a high degree
of accuracy. Any inaccuracy will result in the receiver sampling its input out of
synchronisation with the transmitter, resulting in framing errors.
In order to facilitate the required degree of accuracy whilst accommodating the
very flexible Kinetis clocking system, the UART peripheral uses a 13 bit modulus
counter (SBR) and 5 bit fractional counter (BRFD) to divide the incoming module
clock down to the desired baud rate:
𝐵𝑎𝑢𝑑 𝑅𝑎𝑡𝑒 = 𝑀𝑜𝑑𝑢𝑙𝑒 𝐶𝑙𝑜𝑐𝑘
(16 ∗ (𝑆𝐵𝑅 + 𝐵𝑅𝐹𝐷))
To implement this, UART_Init() first calculates SBR, the integer part of the divisor,
by dividing the module clock by 16 times the desired baud rate (the receiver is
run with 16 times oversampling).
uint16_t calcSbr = (uint16_t) ((moduleClkHz) /
(targetBaudRate * k_oversamplingRate));
As a 5-bit register, BRFD stores a value from 0-31, representing the numerator
of a fraction with denominator 32.
(moduleClkHz * 32) / (targetBaudRate * 16) gives an accurate total divisor for
32 times the required baud rate. Subtracting 32 times the calculated integer SBR
then leaves the required fractional numerator, BRFD.
Chapter 4: Hardware Abstraction Layer Christopher Kerr Serial Communications Interface
27
4.2.2 INTERRUPT-DRIVEN SCI
The UART peripheral can generate interrupts upon completion of transmission
or reception of a byte. This permits the implementation of an interrupt-driven
Serial Communication Interface (SCI). Rather than polling the UART’s status flags,
transmission and reception can occur asynchronously in an interrupt service
routine.
Buffers: First-In, First-Out (FIFO)
Information needs to be sent and received by the Maze Rover asynchronously.
The simplest way to meet this requirement is the addition of send and receive
buffers. Simple First-In, First-Out (FIFO) buffers are implemented by fifo.c.
In this configuration, the reception of data by the UART triggers the UART ISR. If
the Received Data Ready flag is set, the data in the UART data register is stored in
the Receive FIFO. This data can later be retrieved by SCI0_InChar();
When data is ready for transmission, it is added to the Transmit FIFO. If no
transmission is already in progress, the UART’s Transmission Complete interrupt
is enabled. This will immediately trigger an interrupt, as the Transmit Data
Register Empty flag will test true. Data is retrieved from the Transmit FIFO and
placed in the UART’s data register, beginning a transmission. As each
transmission completes, the ISR will be triggered again. If data is successfully
retrieved from the Transmit FIFO, the cycle continues. When no data remains in
the Transmit FIFO, the Transmission Complete interrupt is disabled.
Chapter 4: Hardware Abstraction Layer Christopher Kerr Serial Peripheral Interface (SPI)
28
4.3 SERIAL PERIPHERAL INTERFACE (SPI)
4.3.1 SPI BACKGROUND
Serial Peripheral Interface (SPI) is a full-duplex synchronous serial bus. In this
project, it is used to communicate with the external DAC and ADC peripherals
supplied by the Analog Interface Board.
The Freescale Kinetis DSPI peripheral is very configurable, and offers support
both interrupt-driven and Direct Memory Access usage models. These are
discussed further in Section 4.4: “Analog Interface Board”.
4.3.2 SPI IMPLEMENTATION
Because of the divergent protocol requirements of the DAC and ADC (see Section
4.4), no attempt was made to produce a generalised SPI driver. Instead, spi.c
provides support functions for managing SPI data frames.
The DSPI uses an interesting configuration scheme, in which many configuration
variables are duplicated between the CTARn (Clock and Transfer Attributes)
registers. This allows the module to be reconfigured on a frame by frame basis.
When queuing a frame for transmission, half of the 32-bit PUSHR register is
reserved for configuration bits, including which set of Clock and Transfer
Attributes is to be used for the transmission of that frame (see Figure 10).
Figure 10: PUSHR Register Layout
The function SPI_MakeTxFrame() has been written to assist in handling this data
structure. It has the definition:
uint32_t SPI_MakeTxFrame(uint16_t txData, tSPI_holdPCS holdPCS,
tSPI_CTAR transferSettings, tSPI_EOQ endOfQueue,
tSPI_clearCnt clearCnt, uint8_t assertPCS)
It takes data and configuration settings, and returns a uint32_t which is properly
formatted for assignment to the PUSHR register. This function is as generalised as
can be useful in this project, generating transmission frames which are applicable
to any implementation whether it uses flag polling, interrupts, or DMA.
Chapter 4: Hardware Abstraction Layer Christopher Kerr Analog Interface Board
29
4.4 ANALOG INTERFACE BOARD
4.4.1 OVERVIEW
Figure 11: Analog Interface Board Top Level Schematic (Vambuca, 2013)
The ModCon 2.0 Analog Interface Board is a module for the Freescale Tower
System providing high-quality analog-to-digital (data acquisition) and digital-to-
analogue (data distribution) capabilities. It was designed during a previous
capstone project by Rosario Vambuca, who provided me with an assembled
prototype unit for use in this project.
Data Acquisition
Analog data acquisition is provided by an Analog Devices AD7609 ADC connected
to a SPI interface. This part has the following features:
Eight simultaneously sampled true differential inputs
18 bit resolution
Acquisition rate up to 200,000 samples per second
Data Distribution
Analog data distribution is provided by an Analog Devices AD5754R DAC
connected to a SPI interface. This part has the following features:
Four single-ended channels
Programmable output range (+5V, +10V, +10.8V, ±5V, ±10V, ±10.8V)
16 bit resolution (pin-compatible with 12 bit AD5724R and 14 bit AD5734R)
Chapter 4: Hardware Abstraction Layer Christopher Kerr Analog Interface Board
30
4.4.2 PHYSICAL INTERFACE
The ModCon 2.0 Analog Interface Board was designed to interface to the same
breakout panel as used with the original ModCon in Embedded Software and the
control laboratory. It connects via a 26 pin header cable, carrying signals for 8
differential inputs, 4 single-ended outputs, and ±12V power rails. The remaining
4 pins are connected to analog ground. The breakout panel permits connection of
these signals via standard 4mm banana jacks as seen in Figure 12.
Figure 12: ModCon Analog Interface Breakout Panel
The Analog Interface Board was supplied assembled. I assumed that it had been
demonstrated functional during Rosario’s capstone, but was later informed that
only the power supply had been tested. Initial tests of the Analog-to-Digital
converter revealed an issue – the signals I had connected at the breakout panel
were not present at the ADC input pins. Further investigation showed that the
pinout of the ribbon cable was flipped at the breakout panel. Rosario had copied
the PCB footprint used on the original ModCon Analog Interface, which has the
header soldered on the underside of the board, but populated his board with the
header on the upper side.
IDC header connectors are designed such that you cannot flip the pinout along
either axis by manipulating the connector’s orientation – it is possible to produce
a cable equivalent to having connected Pin 1 to Pin 26, Pin 2 to 25 (i.e. rotated
180 degrees) but not one where Pin 1 connects to Pin 2 and vice versa. The
problem was instead resolved by desoldering the header and installing a new
header on the underside of the board (thanks to Russell Nicholson for his
assistance with the desoldering process).
Chapter 4: Hardware Abstraction Layer Christopher Kerr Analog Interface Board
31
4.4.3 ANALOG TO DIGITAL CONVERTER
As discussed in Section 4.3.2, the DSPI peripheral duplicates the Clock and
Transfer Attributes register, providing CTAR0 and CTAR1. This is convenient,
because the Analog Interface Board has two SPI peripherals, each with its own
format and timing requirements. CTAR0 is configured for use with the AD7609
Analog to Digital Converter.
Handling the Frame Size
AD7609’s serial protocol uses an 18 bit SPI frame, but in master mode the Kinetis
DSPI peripheral only supports frames up to 16 bits. To accommodate this
requirement, I emulate an 18 bit frame by using two 9 bit “sub-frames”.
Figure 13: AD7609 Serial Timing (Analog Devices, 2013, p. 10)
The timing requirements of the AD7609 are shown in Figure 13. It can be seen
that /CS must remain low for the duration of the 18 bit transfer. This can be
achieved using the Continuous Peripheral Chip Select Enable (CONT) flag, which
is located in the PUSHR register and thus may be set independently for each
transmission.
On the first sub-frame, the CONT flag is set. This tells the DSPI to keep the chip
select signal asserted after the transfer completes. On the second sub-frame the
CONT flag is unset, and the chip select returns to the idle state once the transfer
completes.
Baud Rate
The AD7609 is specified for an SCLK frequency of 15MHz when configured with
Vdrive = 3.3V as on the Analog Interface Board.
Chapter 4: Hardware Abstraction Layer Christopher Kerr Analog Interface Board
32
The DSPI peripheral is clocked from the Bus Clock3 (60 MHz). The DSPI baud rate
is set by:
𝑆𝐶𝐾 𝑏𝑎𝑢𝑑 𝑟𝑎𝑡𝑒 = (𝑓𝑆𝑌𝑆
𝑃𝐵𝑅) ∗
1 + 𝐷𝐵𝑅
𝐵𝑅
Where:
PBR is the baud rate prescaler.
Valid values correspond to dividers of 1
2,
1
3,
1
5, and
1
7
DBR is a flag which sets double baud rate mode
BR is the baud rate scaler.
Valid values are 1
2𝑛 from ½ through
1
65536
To achieve 15MHz, we need a total scaling factor of 15
60 =
1
4 , which can be
achieved by setting PBR to ½ and BR to ½, with double baud rate mode disabled.
However, in testing this configuration was found to be unreliable – the ADC
shifted data too slowly, resulting in only 17 bits of data being received. The next
fastest normal baud rate is 10Mhz (PBR = 1
3 , BR = ½), but it is possible to achieve
12MHz by enabling double baud rate mode (DBR = 1, PBR = 1
5 , BR = ½). This
configuration was reliable in testing.
Note that using double baud rate (DBR) mode can result in an asymmetric clock
waveform. This setup gives the worst-case result of 60% high, 40% low.
However, in this case the asymmetry is beneficial. The issue observed at higher
clock rates was that the clock high time was too short for the data to be shifted
out before the sample was taken on the falling edge of the clock. Using DBR gives
a high time roughly equivalent to a 10MHz clock whilst maintaining 12MBit/s
throughput, so transfer times are extended by only 25% compared to the 15MHz
theoretical maximum.
3 The reference manual claims that this module is clocked from the System Clock. This is incorrect.
Chapter 4: Hardware Abstraction Layer Christopher Kerr Analog Interface Board
33
Performing an Analog to Digital Conversion
Figure 14: AD7609 Acquisition Timing (Analog Devices, 2013, p. 9)
The timing of a data conversion is shown in Figure 14. To begin a conversion, the
CONVST4 pin is brought high until the BUSY pin is seen to go high in response.
The chip reacts only to the rising edge of CONVST, so it is permissible for CONVST
to remain high throughout the conversion process. The conversion is complete
on the falling edge of the BUSY line, and can now be read out by supplying up to
8 frames of 18 clock cycles on the SPI bus. Channels must be read sequentially,
but if less channels are required it is permissible to send only the required
number of frames – after a new conversion is performed, the first data shifted
onto the SPI bus is always channel 1.
Implementation Details
A periodic timer is set up to provide an interrupt at the desired sample rate. This
ISR calls Analog_AdcTrigger(), which takes CONVST low. The GPIO pin connected
to BUSY is configured to generate an interrupt on a falling edge, triggering the ISR
Analog_ADC_ConvComplete_ISR() when the falling edge of busy indicates the
conversion is complete. This function sets up the Direct Memory Access (DMA)
module, which reads the results from the SPI without further CPU intervention,
storing them in the array RawResults[]. When the DMA transfer is complete
(having read the requested number of channels) an ISR is triggered which sets
the boolean flag FreshData, which the application may poll to determine when
new data is available. The use of the DMA peripheral is discussed further in the
following section.
The function Analog_Get() takes a channel number as a parameter, and returns
the reassembled 18-bit sample as a signed 32-bit integer.
4 AD7609 provides two CONVST lines. CONVST A starts the acquisition process for the lower four channels, and CONVST B the upper four. The Analog Interface Board ties both pins together, so this document will refer only to CONVST in the singular.
Chapter 4: Hardware Abstraction Layer Christopher Kerr Analog Interface Board
34
4.4.4 DIGITAL TO ANALOG CONVERTER
The AD5754R is part of the AD57x4R family of quad channel Digital to Analog
converters, all of which have a compatible pinout and interface. The cheapest
device, AD5724R, provides a 12-bit converter. The midrange AD5734R provides
a 14-bit converter, and the AD5754R provides a 16-bit converter. No changes to
the circuit or software are required when substituting these parts.
SPI Configuration
The DSPI’s CTAR1 register is configured for use with the AD5754R. Two 12-bit sub-
frames with continuous chip select are used to simulate the required 24-bit
frame. The AD5754R is specified for use with a 30MHz SCLK, and operates
correctly at that speed. See Section 4.4.3 for more details on CTAR configuration.
Figure 15: AD5754R Serial and Load Timing
Serial Protocol
Figure 16: DAC Input Register Layout (Analog Devices, 2011, p. 26)
The AD5754R uses a 24-bit SPI frame, as shown in Figure 16. The most significant
bit is used to indicate whether this is a read to or a write from the addressed
register. Bits REG2-REG0 specify which register is being addressed. Bits A2-A0,
and the data bits, vary in their function depending on which register is addressed.
If addressing the DAC register, A2-A0 specify which DAC channel is to be updated
with the following data. On AD5754R, all 16 bits of data are used for the DAC. On
the cheaper models, the least significant bits are treated as don’t-care values.
Chapter 4: Hardware Abstraction Layer Christopher Kerr Analog Interface Board
35
Startup
When the DAC is reset, it must be initialised before use. Firstly, the output range
must be set in the Output Range Select Register. Both unipolar and bipolar ranges
can be selected, with symmetrical limits at 5V, 10V, or 10.8V.
Secondly, each DAC and the internal voltage reference must be powered up by
writing to the Power Control Register. The DAC’s require 10µs to power up, and
the DAC input register must not be loaded to the DAC during this time.
Optionally, the Control register may be written to disable Serial Data Output. This
feature, which echoes the previous input to the MISO line, is designed to permit
daisy-chaining of DAC’s, but it is not useful in this project.
Loading the DAC
Figure 17: DAC Input Loading (Analog Devices, 2011, p. 22)
After data has been loaded from the SPI input register to the DAC register, the
DAC’s may be updated in one of two modes. If /LDAC is held low whilst the data
transfer is ongoing, the DAC (and thus the output value) is immediately updated
on the rising edge of /SYNC. Alternatively, if /LDAC is high whilst data is loaded,
the update does not take effect until LDAC is held low for at least 20ns. This allows
for all the DAC’s to be updated simultaneously (see visualisation in Figure 15).
This is used in the Maze Rover 2.0 firmware to ensure that DAC updates occur
monotonically.
Chapter 4: Hardware Abstraction Layer Christopher Kerr Analog Interface Board
36
Problems Solved During Testing
In early testing, no output was observed from the DAC. Observation on a mixed
signal oscilloscope showed that whenever /LDAC was high, so was the Analog
Interface Board’s RESET line. Probing with a multimeter confirmed that there
was an electrical connection between /LDAC and RESET, but only when the board
was assembled as part of the Tower System with the TWR-K70F120M.
On the Tower System Connectors, /LDAC is connected to pin B35. The
documentation for the TWR-K70F120M board shows that pin B35 is designated
GPIO4, and is connected to PTB 8 on the microcontroller. However, PTB 8 is also
seen to be used for “RSTOUT_b” on pin A63, which is connected to the RESET line
on the Analog Interface Board. Inspection of the TWR-K70F120M schematics
showed a 0Ω link (see R140 in Figure 18) connecting PTB 8 to the RESET line.
Removing this resistor corrected the behaviour.
Because this modification leaves the RESET line floating, I added a 10kΩ
pulldown resistor from RESET to ground on the Analog Interface board to ensure
RESET was in a known (logic low) state.
Figure 18: Schematic Extract Showing Reset 0Ω Link (Freescale Semiconductor, 2011, p. 8)
In subsequent tests, output from the DAC was erratic. Sometimes the intended
waveform could be seen, but at the wrong scale or offset. Inspecting the Analog
Interface Board schematic, I thought I had found the issue when I saw that the
REFIN pin was not connected to a 2.5V reference. Soldering a connection from
the ADC’s REFOUT pin to the DAC’s REFIN pin resolved the issue, seemingly
confirming my belief. However, it later came to my attention that I had been
reading the wrong datasheet – Analog Devices produce both an AD5754, which
requires an external reference, and an AD5754R. The –R suffixed model includes
an internal reference, but it is powered down by default. Adding the necessary
code to power up the internal reference allowed the DAC to operate correctly
without the additional wire.
Chapter 4: Hardware Abstraction Layer Christopher Kerr Analog Interface Board
37
4.4.5 USING THE ENHANCED DIRECT MEMORY ACCESS PERIPHERAL
Benefits of DMA
The Enhanced Direct Memory Access (eDMA) permits data transfer operations to
be conducted without the intervention of the host CPU. It is capable of performing
its own source and destination offset calculations, permitting it to execute
complex data movements, such as reading incoming SPI values into a buffer. To
perform such operations using interrupts requires a lot of CPU time – every
received SPI frame triggers an interrupt, invoking a context switch every time.
This is particularly significant for the ADC, where transferring the conversion
results for eight channels results in the reception of 16 9-bit subframes.
Furthermore, because the host CPU is fast (clocked at 120MHz), even SPI
transfers are relatively slow – assuming the above transfer incurs no overhead
and takes exactly 144 cycles at 12MHz to complete, in the meantime 1440 cycles
have elapsed at the CPU. In reality, context switching overhead would slow this
further, and some of the remaining CPU time would be spent in the ISR
supervising the data transfer. By replacing such an interrupt-driven system with
DMA, the total transfer time is reduced and the CPU is free to do useful work.
Using DMA with the ADC
Transmission for the ADC is very simple – the MOSI line of the SPI bus is not even
connected to the ADC, so it doesn’t matter what you send provided you supply
the right clocks and framing as discussed in Section 4.4.2. As such, the TX data can
be pre-allocated to an array (AdcQueue[]).
Figure 19: Major and Minor Loops in DMA Requests (Freescale Semiconductor, 2011, p. 586)
Chapter 4: Hardware Abstraction Layer Christopher Kerr Analog Interface Board
38
The DMA Transmission Control Block (TCD) for the ADC TX is configured to move
4 bytes per minor loop (to understand major and minor loops, see Figure 19). The
destination is the SPI2 PUSHR register, and no offset is applied to the destination
address – the PUSHR register never moves. The source address is configured to be
the array of uint32_t AdcQueue[], advancing the source pointer by four bytes after
each transfer (to reach the next 32 bit entry). Only two channels need to be
fetched, so the major loop count is set to 4 (2 frames of 2 sub-frames each), and
the source address is moved back 16 bytes after the major loop is complete,
resetting to the start of the array. The TCD is configured to disable the DMA
request upon completion by setting the Disable Request (DREQ) bit.
For the ADC RX, the configuration is similar, but the source address remains
constant (the SPI2 POPR register) and the destination is the array RawResults[].
Upon completion of the major loop, the RX channel triggers an interrupt (to set
the boolean FreshData flag) and disables the DMA request.
Using DMA with the DAC
For the DAC, the TX configuration is slightly more complex. In order to permit
asynchronous queuing of DAC commands, the TX DMA TCD is configured to use
the DMA’s modulo feature to implement a FIFO circular buffer for the source. The
circular buffer contains 16 uint32_t entries, for a total of 64 bytes, addressed as
0 to 63. 63 is 26 − 1, or 1111112 , so the DMA is configured for a 6 bit Source
Modulo (SMOD). Note that this requires that the array starts aligned with a 64
byte boundary. I initially overlooked this requirement, resulting in very
unpredictable behaviour as the SPI attempted to transmit based on an
uninitialized region of memory. This is fixed by using the proprietary GCC
attribute “aligned”, like so:
uint32_t TxBuffer[BUFFER_SIZE] __attribute__ ((aligned (64)));
This project does not make use of the DAC’s readback feature, so the DMA RX
channel is not configured when using the DAC. However, some code stubs are
included to make implementation of this feature simple if it is desired in the
future. Search for the symbol USE_DAC_RX_DMA.
Chapter 4: Hardware Abstraction Layer Christopher Kerr Analog Interface Board
39
DMA Arbitration
During testing I discovered an issue wherein attempting to connect multiple DMA
channels to the same trigger (i.e. connecting the transmit DMA channels for both
the DAC and the ADC to the SPI2 transmission complete trigger) would result in
unreliable behaviour. Despite having only one channel enabled at a time,
sometimes a channel would simply fail to respond to a trigger.
To solve this issue, I redesigned the DMA system to use only one transmit channel
and one receive channel. This required adding a state machine to manage
arbitration of the DMA channels. In summary, it worked as follows:
At startup, the DMA channels are not configured, and the state machine is in
MODE_UNITIALISED.
When either the DAC or the ADC is used, it checks the current state to see if the
DMA is configured for the active peripheral. Let us assume, for example, that the
ADC is used first after startup. MODE_IDLE_ADC would indicate that the DMA
channels are already configured correctly, but we are in MODE_UNITIALISED, so
the DMA channels are reconfigured for ADC use. Whilst the transfer is ongoing,
the state is set to MODE_ACTIVE, preventing use by the other peripheral. When
the transfer is complete, the mode is set to MODE_IDLE_ADC. If no DAC operations
are performed before the next ADC operation, no reconfiguration will be
required. When a DAC operation is performed, MODE_IDLE_ADC will indicate
that reconfiguration is required, and when the operation is complete the DMA
arbitration state is MODE_IDLE DAC.
Chapter 4: Hardware Abstraction Layer Christopher Kerr Non-Volatile Memory (EEPROM Emulation)
40
4.5 NON-VOLATILE MEMORY (EEPROM EMULATION)
The original Maze Rover software stores many user-configurable constants in
non-volatile memory (NVM). The MC9S12A512 microcontroller at the heart of
the original ModCon provides 4KB of integrated EEPROM, which may be erased
in 4 byte sectors and written in 2 byte words.
The MK70FN1M0 microcontroller used on the TWR-K70F120M development
board does not offer true or emulated EEPROM. The N in the part number
indicates that this model has Program Flash only, whereas an X would indicate
the presence of Freescale’s FlexMemory feature. FlexMemory permits the
designer to partition the available flash between Program Flash and emulated
EEPROM. This feature is present on the MK50DX256 used on ModCon 2.0, but for
this project an alternative solution was required.
The TWR-K70F120M development board offers two external sources of non-
volatile memory – 256MB of NAND flash, and a Secure Digital (SD) card slot.
However, neither of these features is present on ModCon 2.0, so these solutions
would be non-portable. Furthermore, a cursory examination of the relevant
datasheet sections indicated that development of drivers would be non-trivial.
This leaves only the internal source of NVM, the microcontroller’s Program Flash.
Using the Program Flash has the advantage of being available on any Kinetis
derivative, but complicates write operations – writes can be as small as 8 bytes
(a “phrase”) but erases must be a whole 4KB sector. Since NVM is used in this
project to store 1, 2, and 4 byte values which may be updated at runtime, an
EEPROM emulation layer is required to give byte-level read/write access.
4.5.1 USING THE FREESCALE 90NM TFS FLASH DRIVER
Freescale has a driver for interacting with the Program Flash on Kinetis devices,
but it is strangely difficult to find. It can be located by searching for “C90TFS” on
the Freescale website, and locating the entry “TFS Flash Driver Software for
Kinetis and ColdFire+ Microcontrollers”5.
5 At the time of writing the package can be downloaded directly from: http://cache.freescale.com/files/32bit/software/C90TFS_FLASH_DRIVER.exe
Chapter 4: Hardware Abstraction Layer Christopher Kerr Non-Volatile Memory (EEPROM Emulation)
41
The driver package includes a User Manual (Freescale Semiconductor, 2014) and
example projects for various derivatives of the Kinetis architecture including the
MK70FN1M0. Integrating the driver into my project was straightforward – the
driver source and include files were copied into the project tree, and the paths
repaired to correctly reflect the new file layout. The configuration files demo_cfg.h
and user_cfg.h were copied directly from the MK70FN1M0xxx12 demo provided
with the driver.
4.5.2 EEPROM EMULATION
The EEPROM emulation module (Eeprom.c) provides byte-level write access to
the Program Flash by buffering and modifying whole 4KB sectors. The core
functionality of the module is the function Eeprom_Write(), which takes as
parameters a pointer to the first byte of data, the data length in bytes, and a
pointer to the destination address in Flash memory.
Eeprom_Write() is responsible for determining which Flash sector a write affects,
buffering that sector, modifying the relevant part of the buffer, erasing the Flash
sector, and then writing the buffer to the Flash. This process is performed inside
a loop, so the write may cross sector boundaries. The logic is shown in Figure 20.
Figure 20: Eeprom_Write Logic Flowchart
Chapter 4: Hardware Abstraction Layer Christopher Kerr Non-Volatile Memory (EEPROM Emulation)
42
4.5.3 IMPLEMENTATION DETAILS
Program Flash on Kinetis devices is divided into large regions named “blocks” –
on the MK70FN1M0 there are four program flash blocks, dividing the 1MB flash
into 256KB regions. Attempting to read from a block whilst a write is in progress
will result in an access violation error, and the result of the read is invalid. This is
important because program instructions and constant data are stored in the
program flash. Executing instructions or reading data from the same block as an
ongoing write will cause the program to behave unpredictably. This can be
avoided by placing simulated EEPROM variables in a different block to the
program instructions. CodeWarrior Special Edition imposes a 128KB limit on
program size, and the CodeWarrior new project wizard generates a linker map
which places the program at the start of the address space. Thus it may be
assumed, since 128KB is less than 256KB, that any block beyond the first is
available at any time.
However, I wished to provide a more generalised solution. This involves
executing the function which actually performs the Flash operations from RAM,
thus avoiding the need to read from Flash whilst the operation is ongoing. The
Freescale flash drivers provide a function designed for this purpose named
RelocateFunction(), and use of this function to store FlashCommandSequence() in
RAM was simple to integrate into Eeprom_Init().
However, during testing, writes to the first block still caused the program to
crash. I initially assumed the debugger was giving faulty data – it indicated that
execution had followed a function pointer to address 0x00000000. However,
debugging showed that this was exactly the case – an interrupt had caused a read
from the interrupt vector table, which returned invalid data resulting in an
invalid function pointer dereference. Interrupts must be completely disabled
around the write operation if it is to complete safely.
This functionality is not enabled by default, because it presents the risk of
overwriting the program data. The functionality permitting writing to the first
block may be configured by defining or undefining the symbol EEPROM_ANY_BLOCK.
The limits of the simulated EEPROM region are set by the pointers
k_EepromStartPtr and k_EepromEndPtr.
Chapter 5: High Level Functionality Christopher Kerr Maze Rover to PC Communications
43
5 HIGH LEVEL FUNCTIONALITY
5.1 MAZE ROVER TO PC COMMUNICATIONS
The Maze Rover is configured using the Maze Rover PC Interface, an application
running on a Windows PC. The PC Interface communicates with the Rover via an
emulated UART, using a modified version of Dr McLean’s ModCon Serial Protocol.
5.1.1 MODCON SERIAL PROTOCOL
Structure
The ModCon Serial Protocol (McLean, 2013) defines a 5-byte packet structure as
shown in Figure 21 below.
Command Parameter 1 Parameter 2 Parameter 3 Checksum
Figure 21: Serial Protocol Packet Structure
The most significant bit of the command byte is reserved for packet
acknowledgement requests. The remaining 7 bits store the command number,
thus permitting 128 unique commands (0x00 – 0x7F).
The function of the three parameter bytes may vary depending on the command.
The final byte carries a checksum, which is simply the exclusive-or (XOR) of the
preceding four bytes. Whilst not very theoretically robust (compared to popular
checksum methods like Cyclic Redundancy Checking), this checksum is
computationally inexpensive and quite adequate in this application. The
emulated UART used to communicate with the Maze Rover is implemented using
the USB Communications Device Class, which includes its own robust methods
for ensuring data integrity.
Packet Acknowledgement
The sender requests acknowledgement from the receiver by setting the most
significant bit (the ACK bit) of the Command byte. The receiver first attempts to
complete the requested command. If successful (or if no action is required), it
returns a copy of the packet with the ACK bit set. If unsuccessful, the packet is
modified to unset the ACK bit before being returned, thus sending a NACK.
Chapter 5: High Level Functionality Christopher Kerr Maze Rover to PC Communications
44
5.1.2 IMPLEMENTATION DETAILS
Most of the implementation is straightforward, and neither novel nor interesting.
packet.c contains functions for recognising valid received packets (Packet_Get())
and assembling packets for transmission (Packet_Put()). When a valid packet is
received, it is then passed to Protocol_HandlePacket(). This function handles
processing received packets, selecting the appropriate function based on the
received command. Implemented as a switch statement, it looks like this:
switch(maskedCommand)
case CMD_STARTUP:
retVal = Packet_HandleStartup();
break;
case CMD_TIME:
retVal = Packet_HandleTime();
break;
... // and so on, for many cases
Whilst this construct is simple and easily maintained at this size, the function may
potentially be required to scale to 128 entries. The resulting function would be
long and difficult to maintain. I prefer to address this problem with an array of
function pointers, and discuss the practicality of this solution below.
Function Pointers
In C, a pointer named funcPtr to a function which takes a Packet_t as a parameter,
and returns a bool, can be declared like so:
bool (*funcPtr)(Packet_t);
This syntax, which arises from C’s “declaration follows usage” design pattern, is
difficult for readers to parse. It is typically preferred to use a typedef to name
function pointers of a given signature (parameters and return type) as a type,
permitting a more typical declaration. The following extract creates a pointer
named funcPtr to a function Protocol_HandleStartup(), then dereferences that
pointer to call the function (note that in C, &fooFunc and fooFunc are equivalent –
either gives the address of the function fooFunc):
typedef bool(*Protocol_jumpPtr_t)(const Packet_t);
Protocol_jumpPtr_t funcPtr = Protocol_HandleStartup;
bool retVal = *funcPtr(fooPacket);
Chapter 5: High Level Functionality Christopher Kerr Maze Rover to PC Communications
45
Switch Statements vs Function Pointer Arrays
An array of function pointers named jumpTable may be created like so:
typedef bool(*Protocol_jumpPtr_t)(const Packet_t);
static const Protocol_jumpPtr_t jumpTable[] =
Packet_HandleStartup, // 0x00
Packet_handleDefault, // 0x01 – Unused
Packet_HandleTime // 0x02
;
Now the appropriate function for handling the command stored in an
appropriately range-tested variable named maskedCommand can be called by:
bool retVal = *jumpTable[maskedcommand](fooPacket);
The advantages of using an array of function pointers are:
Reduced logic complexity – new functionality can be added by altering only
the statically-allocated array, without need to modify the program logic
Improved speed – each case in a switch statement is tested sequentially, and
conditional logic is slow. For switches with many cases, the time spent
processing the switch becomes significant, so such designs scale poorly.
However, there are also some disadvantages:
C does not test if an array index actually lies within the bounds of the array,
so the index value must be correctly range limited before use.
Unused cases must be handled – there is no safe way to skip inclusion of an
element in a function pointer array, so a dummy function must be inserted for
all unused indices. For Protocol_HandlePacket(), the dummy function simply
returns false without performing any other action.
As a result of this second drawback, the function pointer array must be at least as
large as the difference between the lowest expected index (which can be offset to
0 by a subtraction) and the highest. However, if the array is declared static const
it will be stored in Flash, where bytes are plentiful. Thus, I chose to include the
full range of possible values from 0x00 to 0x7F in my jump table, with each entry
commented as shown in the example above. This makes it easy to find the entry
for a given command code, and to visually distinguish unused codes.
Chapter 5: High Level Functionality Christopher Kerr Maze Rover Communications Functions
46
5.2 MAZE ROVER COMMUNICATIONS FUNCTIONS
The communications functions are a direct port of those written by Dr McLean
for the original Maze Rover. I will briefly outline their function, and discuss the
adaptations required to have them function correctly in my project.
5.2.1 MODULATED WAVEFORM GENERATION
Comms_MakeWave() pre-calculates an amplitude modulated waveform, storing it in
the array WaveData[]. Interestingly, this function does not actually perform
amplitude modulation, but rather emulates it by adding sinusoidal components.
When considered in the frequency domain, it can be seen that this produces the
same output signal as amplitude modulation, but only if the message signals are
sinusoids.
The only significant changes made to this function were:
Updating the cosine-wave table to provide full 16 bit resolution. This was not
strictly necessary, but seemed proper – the new platform uses a proper DAC
in place of the smoothed PWM of the original Maze Rover, making it capable
of much higher fidelity.
Updated data types to ensure results would not be truncated as a result of the
16-bit cos wave input
A third change which was not made, but may be considered, relates to the divisor
used to scale the summed waveforms to an int16_t for output. I have kept the
same value as the original ModCon (21 * 2 = 42), but this results in an output
which is 10Vpp rather than the 5Vpp of the original system. It is my opinion that
the 5Vpp output used in the original Maze Rover was merely the result of its
technical limitations, and thus have made no deliberate effort to replicate this
characteristic.
5.2.2 VOLTAGE CONTROLLED OSCILLATOR
The function Comms_UpdateVCO() implements a numerically controlled sinusoidal
oscillator. When provided with ADC samples as input, it may be considered a
Voltage Controlled Oscillator. No modifications to this function were performed,
but it is necessary to modify the way in which it is called in order to preserve its
Chapter 5: High Level Functionality Christopher Kerr Maze Rover Communications Functions
47
intended performance. When the numerical input is supplied by the Analog
Interface Board’s 18-bit ADC, it is necessary to divide by 64 to preserve the
intended voltage-to-frequency relationship. However, this change should not be
made internal to the function, as this would render it incompatible with the
Phase-Locked Loop.
5.2.3 PHASE LOCKED LOOP
No changes to the PLL were required, but during testing (see Section 0) it was
noted that the PLL performed well over only a very limited range of frequencies,
perhaps 250Hz either side of the VCO’s centre frequency. This may merit further
investigation, but I spent no further time on it given that the demodulator, which
relies upon the PLL to resynthesise the carrier wave, operates without issue.
5.2.4 DEMODULATION
Comms_DetectDemod() implements a coherent demodulation system as outlined in
Figure 22. The modulated waveform is multiplied by the oscillator recovered by
the phase-locked loop, then filtered by a comb-resonator cascade.
Figure 22: Coherent Demodulation Scheme
However, the resulting signal is not used as the output. Instead, a Sliding Discrete
Fourier Transform is used to determine the magnitudes of the expected message
frequencies, and these magnitudes are used by Comms_MakeDemod() to synthesise a
clean version of the demodulated signal.
5.2.5 TESTING
This project is focused on embedded software development, not signal theory. As
such, I have used the pre-solved “Automatic” coefficients for testing, rather than
presenting my own derivation.
Chapter 6: Communication Lab Testing Christopher Kerr Modulated Wave Generation
49
6 COMMUNICATION LAB TESTING
6.1 MODULATED WAVE GENERATION
Single Sideband Lower
Figure 23: SSB-Lower Time Domain
Figure 24: SSB-Lower Frequency Domain
Rover Spec Carrier Freq. Msg 1 Freq. Msg 2 Freq.
Odd Autumn A 1050 Hz 375 Hz 425 Hz
Chapter 6: Communication Lab Testing Christopher Kerr Modulated Wave Generation
50
Single Sideband Upper
Figure 25: SSB-Upper Time Domain
Figure 26: SSB-Upper Frequency Domain
Rover Spec Carrier Freq. Msg 1 Freq. Msg 2 Freq.
Odd Autumn I 1100 Hz 75 Hz 125 Hz
Chapter 6: Communication Lab Testing Christopher Kerr Modulated Wave Generation
51
Double Sideband
Figure 27: SSB-HI Time Domain
Figure 28: SSB-HI Frequency Domain
Rover Spec Carrier Freq. Msg 1 Freq. Msg 2 Freq.
Odd Autumn E 700 Hz 425 Hz 375 Hz
Chapter 6: Communication Lab Testing Christopher Kerr Voltage-Controlled Oscillator
52
6.2 VOLTAGE-CONTROLLED OSCILLATOR
For this test I have used the configuration of Maze Rover B, Spring Semester 2013,
in order to permit comparison to the known results of an original Maze Rover.
The centre frequency for this configuration is 1100Hz.
Input Voltage Frequency (tested) Frequency (sample)
-2.5 1474.1 1497
-2.25 1436.7 1452
-2 1400.0 1417
-1.5 1324.2 1341
-1 1249.1 1267
-0.5 1174.2 1192
0 1099.6 1110
0.5 1024.5 1035
1 949.4 958
1.5 874.3 885.3
2 799.3 812.7
2.25 761.8 774
2.5 724.3 749.9
Notice that the tested values which are multiples of 0.5 volts give frequencies
which are very close to multiples of 25. In my opinion, this suggests that my
measurements are more accurate than those I am comparing against. This is
supported by the linear regression shown in Figure 29.
Figure 29: Voltage Controlled Oscillator Characteristic
Sample:y = -150.77x + 1114.7
Tested:y = -150x + 1099.3
600
700
800
900
1000
1100
1200
1300
1400
1500
1600
-2.5 -1.5 -0.5 0.5 1.5 2.5
Ou
tpu
t F
req
uen
cy (
Hz)
Input Voltage (V)
Frequency (sample)
Frequency (tested)
Chapter 6: Communication Lab Testing Christopher Kerr Phase Locked Loop
53
A note on the testing procedure: It is typically difficult to measure the frequency
of unfiltered DAC-synthesised sine waves, since features are not typically placed
consistently between cycles. However, when using a digital storage oscilloscope
such as the Agilent DSO-X 2004A used in these tests, the Fast Fourier Transform
functionality provides a useful method for making accurate measurement
measurements of frequency. The high-order frequency components which make
up the “stair-step” characteristic are easily distinguished from the fundamental
when viewed in the frequency domain.
6.3 PHASE LOCKED LOOP
Figure 30: Phase-Locked Loop Operation
The Phase-Locked Loop works very well over a limited range of frequencies, but
performance rapidly degrades as the input frequency diverges from that shown
in Figure 30. Debugging did not reveal any obvious inconsistency in operation.
Both the Voltage Controlled Oscillator and the demodulator operate correctly,
and these components are closely related to the Phase Locked Loop.
Chapter 6: Communication Lab Testing Christopher Kerr Demodulator
54
6.4 DEMODULATOR
For this test, the same configuration was used as in Section 6.2 (Rover B, Spring
Semester 2013). This configuration uses single-sideband upper modulation, with
messages frequencies 300Hz and 350Hz. These frequencies have been perfectly
reconstructed, as seen in Figure 32.
Figure 31: Demodulated Message Signal Time Domain
Figure 32: Demodulated Message Signal Frequency Domain
Chapter 7: Control Implementation Christopher Kerr Controlled Plant: Quanser MAGLEV
55
7 CONTROL IMPLEMENTATION
Finalised Maze Rover 2.0 hardware was not available during the development of
this project, so implementation and testing of the Maze Rover’s Model Reference
Adaptive Controller could not be performed. Instead, an alternative control task
was implemented on this platform using the Quanser MAGLEV apparatus,
demonstrating the viability of ModCon 2.0 for performing complex control tasks.
7.1 CONTROLLED PLANT: QUANSER MAGLEV
Figure 33: Quanser MAGLEV Apparatus
The Quanser MAGLEV plant, seen in Figure 33, is an electromagnetic suspension
system. One pole of an electromagnet faces downward towards a post on which
a stainless steel ball sits when at rest. The pole contains a photo-transistor based
sensor which measures the distance from the top of the post to the surface of the
ball. A one Ohm current-sense resistor is provided, giving a 1 VA-1 response.
Using this apparatus, the goal of the controller is to modulate the input voltage of
the electromagnet such that ball tracks to a specified point in the vertical axis.
The derivation of a system model and LQR controller for this plant can be found
in Appendix B.
Chapter 7: Control Implementation Christopher Kerr Control Framework
56
7.2 CONTROL FRAMEWORK
In order to perform control tasks on the ModCon 2.0 platform, a generalised
control framework was constructed, mimicking the public interface of the control
library provided by Dr McLean for the original ModCon. An overview of the
control framework software flow is shown below in Figure 34.
Figure 34: Control Module Overview
The original ModCon control library is supplied in binary form, hiding its
implementation details. However, its public interface allowed me to infer a likely
structure and implement a similar system. Note that the original ModCon control
library is designed to be controlled by the ModCon PC Interface, which uses
automatic port detection logic incompatible with ModCon 2.0. Due to time
limitations functionality related to the PC interface, such as input/output channel
visualisation and runtime controller configuration, was not implemented. Only
the most basic functionality required to control a system is presented here.
As discussed in 4.4.3: “Analog to Digital Converter”, data acquisition is performed
as a background DMA task triggered by a timer interrupt. When acquisition is
complete, a flag is set which is tested in the main loop. When the test is successful,
Control_BackgroundTasks() is called, starting a control cycle.
Dr McLean’s control template project includes one user-editable file, control.c,
containing only one function, Control_DoControl(). This file exposes three two
dimensional arrays – the input, output, and intermediate values. For each array,
the first dimension corresponds to a channel (or intermediate state). The second
dimension stores past values, with the current state indexed as 0, the value from
the previous control period at index 1 and so on. The control algorithm is
implemented by the user in Control_DoControl(), writing the calculated outputs
to the 0 index of the output channel array members. The library then invisibly
handles the details. My control framework defines four functions which mimic
the known behaviour of Dr McLean’s library.
Chapter 7: Control Implementation Christopher Kerr Control Framework
57
Control_getData()
This function converts and scales the new ADC samples to a floating point value
measured in volts, storing the result in Input[chanNb][0].
Control_doControl()
As in Dr McLean’s original template, this function is modified by the user to
perform the actual control algorithm, writing the results to Output[chanNb][0].
The array Var[][] is provided to store intermediate values and their history,
which is useful for implementation of difference equations for filtering,
differentiation, integration, and so on.
Control_putData()
This function scales the Output[chanNb][0] values by 3276.7, converting their
range from ±10V to the scale used by the DAC. Values are hard-limited to ±32,767
(the bounds of a 16 bit signed integer), then converted to an int16_t for
Analog_Write(). After all channels have been calculated and buffered by
Analog_Write(), the function calls Analog_Push() to start the SPI transfer to the
DAC.
Control_rotate()
This function maintains the history states of Input[][], Output[][] and Var[][],
moving each Variable[i][n] to Variable[i][n+1]. It takes parameters for the
number of input, output, and intermediate channels, and the number of history
states to keep. These parameters are, in practice, always set from a const
configuration value, but exposing parameters is more in line with the project
coding standard than use of a file-scope constant.
Other Functions
In addition to the functions described above, I have included a function for
performing discrete integration, which is passed a pointer to a struct containing
the accumulated integral and the maximum and minimum limits for that value.
Such limits are useful for preventing integral windup, and encapsulation as a
function cleans up the control code considerably. Such a function was not
included for discrete differentiation, because it is typical to perform difference-
equation-based filtering rather than simply hard-limiting the discrete derivative.
Chapter 7: Control Implementation Christopher Kerr Controller Implementation
58
7.3 CONTROLLER IMPLEMENTATION
7.3.1 HARDWARE FLOATING POINT
For maximum flexibility, a new CodeWarrior project was created using the
Kinetis K70’s Hardware Floating Point capabilities. The entire source tree of the
Maze Rover 2.0 project was then imported. No modifications (other than those
described for the creation of a new project in 3.1.3: Using C99) were required.
The new project compiled and ran without issue.
7.3.2 SAMPLE RATE
A sampling rate of 1ms was selected, based on simulations showing that the
system was unstable at 2.5ms or greater. Testing with a Mixed Signal Oscilloscope
showed that the control cycle was complete in less than one-fifth of that time,
with the time spent processing the control algorithm insignificant compared to
the time required to interface with the analog components over SPI. Despite the
possibility of increasing the sample rate, I chose to keep the sample rate at the
conservative value of 1ms because the equipment required to visualise and debug
digital logic was not available in the control laboratory.
7.3.3 ANALOGUE BUFFER
During testing, some issues were observed with the digital to analogue converter.
Rapid voltage swings exceeding approximately 11V, as seen when the ball first
overshoots the setpoint, would result in the DAC’s protection circuitry being
triggered. This would cause the output voltage to be clamped to ground until the
system was reset.
There was insufficient time to fully investigate this phenomenon, which I believe
may have been due to the characteristic impedance of the test leads in use. The
symptoms were resolved by the addition of an op-amp configured as a simple
voltage follower (non-inverting buffer) like that shown in Figure 35.
Figure 35: Non-Inverting Op-amp Buffer
Chapter 7: Control Implementation Christopher Kerr Controller Implementation
59
7.3.4 ARCHITECTURE
Figure 36 : Controller Architecture
The controller architecture as implemented is shown in Figure 36. This
architecture has several minor modifications to a typical state feedback system,
described below.
PID Form
Inspecting the state-space form of the system, it is clear that a full-state feedback
controller has the form of a PID controller – a gain applied to the proportional,
integral, and derivative terms of the ball position. This form has a simple
advantage over a typical full-state-feedback design – the reference is compared
directly to the system output, rather than to the sum of all state variables, thus
avoiding the need to precompensate the reference.
A typical modification to PID systems, to improve stability, is to apply the
derivative gain directly to the process variable, rather than using the error
between the system output and the reference. Recognising that this system is
essentially a PID controller allows this optimisation to be applied here.
It can be shown in Matlab that the modified feedback system produces the same
closed-loop poles as designed – the systems are equivalent, but this version is
simpler to conceptualise.
Chapter 7: Control Implementation Christopher Kerr Controller Implementation
60
Feedforward Gain
The controller as designed relies on the assumption that it is correcting small
deviations in the “linear” region around the setpoint.
For a given position reference, the Feedforward Gain supplies the approximate
steady-state current required to maintain that position. If we assume the
characteristic to be linear, we may calculate the Feedforward Gain, Kff, based on
the linearisation parameters:
𝐾𝑓𝑓 =𝑖𝑠𝑠
𝑥𝑠𝑠
To improve the performance, the real average current for a variety of positions
was measured and plotted, and a linear fit to the data was found by regression.
This works well over a limited range of the ball’s travel, but poorly at the
extremes. To improve the performance, the real average current for a variety of
positions was measured and plotted, and a linear fit to the data was found by
regression (see Figure 37 below).
Figure 37 : Feedforward Current Relationships
It can be seen in Figure 37 that the relationship is not linear, but is a good fit to a
second-order relationship. However, it can also be seen that a good piecewise-
linear approximation can be made from two sections. For processing efficiency,
this second option was used in the final implementation.
y = 84x + 0.7466R² = 0.9962
y = 165.6x + 0.3377R² = 0.9978
y = -8.8x2 + 217.3x + 266.8R² = 0.9978
0
0.2
0.4
0.6
0.8
1
1.2
1.4
1.6
1.8
2
0 2 4 6 8 10 12
Chapter 7: Control Implementation Christopher Kerr Controller Implementation
61
7.3.5 LAB TESTING RESULTS
Figure 38 : Lab Response to Step Input
Unfortunately, a hardware flaw made large step inputs untestable – when the
control effort swung rapidly from a large positive output to a large negative
output, the Digital to Analogue converter’s protections were triggered, shutting
down the device. However, small steps could be tested successfully. We observe
a maximum steady state error of 2%, rise time less than 100ms, peak overshoot
of 6.7% and 5% settling time less than 200ms.
Note that some low-amplitude oscillation is visible in the steady state. This is not
a fault in the performance of the controller, but rather reflects the limitations of
the apparatus. Throughout this report it is assumed that the ball travels in only
one axis (vertically), but in reality the ball is unconstrained in all 3 axes. Motion
on the horizontal axes is uncontrolled, as no actuator capable of effecting such
motion is provided. Due to the nature of the distance measurement (an optical
sensor pointing at the bottom of the ball) side-to-side motions are erroneously
detected as movement in the vertical axis. Unfortunately this causes the
controller to attempt correction of apparent vertical motion which doesn’t
actually exist.
Chapter 7: Control Implementation Christopher Kerr Controller Implementation
62
Figure 39 : Lab Tracking of Triangle Wave Input
Over most of the ball’s range of motion the setpoint is tracked very accurately.
However, the tracking becomes inaccurate when the ball gets close to the
electromagnet – in this region the linearised model is no longer valid, so the
controller operates poorly. This is expected, but steps have been taken to
minimise its impact (see Feedforward Gain in the previous section).
Figure 40 : Lab Rejection of External Disturbance
In this test an external disturbance was introduced by sharply striking the bench
near the apparatus. This imparted a measurable vibration, seen above at t = 1.5
seconds. By t = 2.5 seconds the oscillation has been completely corrected and the
system returns to the steady state.
7.3.6 CONTROL IMPLEMENTATION CONCLUSIONS
The successful implementation of this controller indicates that the ModCon 2.0 is
an appropriate platform for complex control tasks.
-2.0
0.0
2.0
4.0
6.0
8.0
10.0
12.0
0.0 2.0 4.0 6.0 8.0 10.0
Time (seconds)
Tracking Triangle Wave Input
Position (mm)
Setpoint (mm)
Error (mm)
5.6
5.8
6.0
6.2
6.4
0.0 1.0 2.0 3.0 4.0
Rejection of External Disturbance
Position (mm)
Setpoint (mm)
Chapter 8: Conclusions Christopher Kerr Final Project Status
63
8 CONCLUSIONS
8.1 FINAL PROJECT STATUS
At the conclusion of this project, I have achieved my core goals. To quickly review:
My review of the literature and development of an Embedded Software Coding
Standard has brought me a thorough understanding of shortcomings of the C
language, the need for quality code in embedded systems, and how these issues
can be addressed by rigorous application of best practice.
I have learnt and documented the process of developing software for Kinetis
microcontrollers using the Freescale CodeWarrior IDE. Even if I do not work with
this platform again in future, the process of learning a new environment and
architecture has developed skills which will be transferrable to further
architectures in the future. Furthermore, as Kinetis is based on the commonly
used 32-bit ARM Cortex-M4, I will have some familiarity when developing on
other ARM-based microcontrollers (such as the Texas Instruments Tiva-C and
ST Microelectronics STM32 platforms). I hope that my documentation will also
help the process of integrating the ModCon 2.0 into the UTS laboratories.
A hardware abstraction layer providing access to the peripherals required by the
Maze Rover 2.0 has been developed, and its functionality demonstrated by
porting the Maze Rover’s communications functionality to this platform. The
communications functions are now partially documented, improving upon their
state in the original Maze Rover.
Finally, a complex control algorithm has been demonstrated on the Maze Rover
2.0 platform. This task was performed in lieu of developing the Model Reference
Adaptive Controller for the Maze Rover’s motors, as final Maze Rover 2.0
hardware was not available as of the conclusion of this project.
Chapter 8: Conclusions Christopher Kerr Project Management and Scheduling
64
8.2 PROJECT MANAGEMENT AND SCHEDULING
This project encountered a number of difficulties which resulted in a significant
alteration of project focus and scope.
The first was simple optimism – despite the warnings of my supervisor, the
original scope was extremely ambitious, aiming to complete the objectives
covered by this report in half the time. My only concession to contingency
planning was describing the project in discrete stages which could easily be
removed – in this final version of the report, none of the “extensibility” aspects of
the original proposal are addressed, but the core functionality is complete.
Secondly, I encountered an early setback related to my plans to use C++. This was
discussed further in 3.1.2: “Language and Compiler – Exploring the Use of C++”.
Investigating this issue and exploring alternatives unexpectedly consumed
approximately two weeks of my scheduled project time, and ultimately lead to a
significant redirection of the project focus. Where originally I had intended to
explore the applicability of Object Oriented and Functional programming
paradigms to embedded development, instead the project focus turned to the
process of developing quality embedded code.
However, the major impact on this project’s progress relates to the Analog
Interface Board. My original proposed schedule was based on a faulty
assumption: namely that the Analog Interface Board had been fully tested and
documented during the project that produced it. I scheduled only a brief period
for developing the Hardware Abstraction module for this device on the
assumption that it would only involve the integration of existing drivers. These
assumptions proved to be unfounded – the board was untested and no driver
interface existed. Producing this module ultimately took several months of work,
and was delayed over the Christmas break due to Dr McLean’s scheduled leave.
Ultimately these delays were largely my own fault, despite the influence of factors
out of my control. Had I put more care into the preparation of my proposal, these
difficulties could have been anticipated and contingencies prepared. The
management of these issues by reduction of project scope has produced a merely
satisfactory outcome (the delivery of core Maze Rover functionality).
Chapter 8: Conclusions Christopher Kerr Further Work
65
8.3 FURTHER WORK
Model Reference Adaptive Motor Controller
As final Maze Rover 2.0 was not available in time for this project, the Model
Reference Adaptive Controller for the motor driver was not implemented. This
will need to be addressed before Maze Rover 2.0 is deployed.
Use of C++
During the writing of this report, I became aware of a new software release from
Freescale – the Kinetis Design Studio IDE. Currently in beta, this software has one
important difference to CodeWarrior – it is completely free, but supports C and
C++ without code size restrictions on all Kinetis devices. I recommend that future
development efforts on the Kinetis platform use this hobbyist-targeted tool
instead of the crippled “Special Edition” of CodeWarrior.
Analog Interface Board
Using the ModCon 2.0 Analog Interface Board in this project revealed some quirks
which may merit a new revision before laboratory deployment.
Firstly, the board provides no overvoltage protection beyond that the ADC
includes internally. The ADC input clamps tolerate only the limited range ±16.5V,
which is easily exceeded when interfaced with common UTS laboratory
equipment such as the MiniLab which can produce up to +30V. I recommend that
a more robust protection, such as an external clipper diode network, be
implemented in a future revision.
Secondly, the DAC has weak output drive. During testing its overcurrent
protections were activated when interfacing with equipment in the control
laboratory. I recommend that future revisions include an output buffer for each
channel, which could be as simple as a quad op-amp.
Finally, I note that the addition of a single trace from the ADC’s RefOut pin to the
DAC’s RefIn pin would allow the substitution of the cheaper non-suffixed AD57x4
DAC’s. These parts are pin-compatible with the AD57x4R models. As the internal
reference on the –R models is disabled by default, either version could be used
based on availability.
Chapter 8: Conclusions Christopher Kerr Further Work
66
Potential Improvements and Future Projects
The Maze Rover 2.0 platform is built on the Freescale Tower System, opening the
opportunity for a number of potential future projects. Some examples include:
Human Machine Interface using touch-screen LCD backpack
Wireless network interface providing remote configuration and control
using Wi-Fi module
Furthermore, the Kinetis platform at the heart of Maze Rover 2.0 provides the
opportunity to exploit Freescale’s MQX Real-Time Operating System. In addition
to the scheduling and timing flexibility provided by an RTOS, MQX includes
drivers for TCP/IP networking, SD cards, USB device support, and other high-
level functionality facilitating the development of complex projects on this new
platform.
References Christopher Kerr
67
REFERENCES
Analog Devices, 2011. AD57x4R Datasheet Rev. E. [Online]
Available at:
http://www.analog.com/static/imported-files/data_sheets/AD5724R_5734R_5754R.pdf
[Accessed 20 December 2013].
Analog Devices, 2013. AD7609 Datasheet Rev. B. [Online]
Available at: http://www.analog.com/static/imported-files/data_sheets/AD7609.pdf
[Accessed 20 December 2013].
Barr, M., 2013. Barr Group Embedded C Coding Standard. Gaithersburg, MD: Barr Group.
Freescale Semiconductor, 2011. K70 Family Reference Manual Rev. 2. [Online]
Available at:
http://cache.freescale.com/files/microcontrollers/doc/ref_manual/K70P256M150SF3RM.pdf
[Accessed 30 July 2013].
Freescale Semiconductor, 2011. TWR-K70F120M Schematic Rev. B1. [Online]
Available at:
http://cache.freescale.com/files/32bit/hardware_tools/schematics/TWR-K70F120M-SCH.pdf
[Accessed 26 February 2014].
Freescale Semiconductor, 2012. Kinetis Quick Reference User Guide Rev. 2. [Online]
Available at: http://cache.freescale.com/files/32bit/doc/quick_ref_guide/KQRUG.pdf
[Accessed 17 October 2013].
Freescale Semiconductor, 2012. TWR-K70F120M Tower Module User's Manual Rev. 1.1. [Online]
Available at:
http://cache.freescale.com/files/microcontrollers/doc/user_guide/TWRK70F120MUM.pdf
[Accessed 1 June 2013].
Freescale Semiconductor, 2014. Standard Software Driver for C90TFS/FTFx Flash User's Manual.
[Online]
Available at: http://cache.freescale.com/files/32bit/software/C90TFS_FLASH_DRIVER.exe
[Accessed 15 April 2014].
Ganssle, J., 2008. Art of Designing Embedded Systems. 2nd ed. Burlington, MA: Elsevier.
McLean, P., 2013. ModCon Serial Protocol. [Online]
Available at:
http://services.eng.uts.edu.au/pmcl/embsw/Downloads/ModConSerialProtocol.pdf
[Accessed 1 July 2013].
Chapter 0: References Christopher Kerr Further Work
68
McLean, P., 2013. Software Style Guide. [Online]
Available at: http://services.eng.uts.edu.au/pmcl/embsw/Downloads/SoftwareStyleGuide.pdf
[Accessed 1 November 2013].
MIRA Limited, 2004. MISRA-C:2004 Guidelines for the use of the C language in critical systems.
2nd ed. Nuneaton, UK: MIRA Limited.
Styger, E., 2012. MCU On Eclipse - Please Check Your License. [Online]
Available at: http://mcuoneclipse.com/2012/08/06/please-check-your-license/
[Accessed 1 August 2013].
Vambuca, R., 2013. Analog Board Schematics, s.l.: s.n.
Table of Appendices Christopher Kerr
69
TABLE OF APPENDICES
Appendix A: Embedded C Coding Standard .................................................................... A-1
Appendix B: Control System Modelling and Design ................................................... A-29
Appendix C: Datasheets ......................................................................................................... A-43
The Appendix CD should be found affixed to the inside rear cover of this report.
If the CD is missing, damaged, or otherwise unreadable, a copy can be found on
my personal website at:
http://cjk.net.au/capstone/
Appendix D: Maze Rover 2.0 Code .................................................................................On CD
..................................................................................On CD Appendix E: Control System Code
Appendix F: Maze Rover 2.0 Technical Reference....................................................On CD
Appendix A: Coding Standard Christopher Kerr Chapter 1: Preamble
A-1
Appendix A: Embedded C Coding Standard
1 PREAMBLE 1.1 VERSION CONTROL
Rev. Date Comment Authors
1.2 2014-04-28 Added Floating Point Types Christopher Kerr
1.1 2014-04-11 Updated rules regarding parenthesis
use after considering the Apple
“GotoFail” and OpenSSL “Heartbleed”
vulnerabilities.
Christopher Kerr
1.0 2013-12-06 Initial Revision. Incomplete but in
use.
Christopher Kerr
1.2 INTRODUCTION The purpose of this document is to assist in the production of quality code,
defined here to be:
Stable: The software should have no known bugs, and all necessary
precautions must be taken to avoid introducing common errors.
Maintainable: The intent of code must be clear to the reader, and the
code’s purpose in the larger system must be thoroughly documented.
Portable: The code should not make unnecessary assumptions about the
underlying architecture or compiler, to permit porting between systems.
The embedded environment is fundamentally different from the personal
workstation environment with which most programmers are familiar. The
operation of embedded systems is largely autonomous and unsupervised.
Furthermore, many embedded systems control safety-critical processes, such as
automobiles and industrial automatons. In this environment, unreliable software
can result in injury or death.
Appendix A: Coding Standard Christopher Kerr Chapter 1: Preamble
A-2
Unfortunately, as a language, the C standard tends towards permissiveness. It is
certainly possible to write code which will compile as valid C despite being
difficult to understand, perverse, or not correctly reflecting the intentions of the
programmer. Other sections of the language are well defined but prone to
programmer error, such as the operator precedence rules.
Given these limitations of C, it is clear that the language must be applied carefully
when used for embedded systems. Used without restriction, C has features which
can negatively impact the stability, maintainability, and portability of code,
resulting in poor code quality.
1.3 SOURCES This Coding Standard has been assembled using the following references:
i. MISRA-C:2004 – Motor Industry Software Reliability Association
ii. Barr Group Embedded C Coding Standard – Michael Barr
iii. The Art of Designing Embedded Systems – Jack Ganssle
iv. Embedded Software Style Guide – Peter McLean
It incorporates significant material from each of the listed sources, with
modifications reflecting my own preferences.
Of these, the first two are considered to be definitive – where this document
contradicts either of these standards, the contradiction is to be noted and
rationalised.
The second two documents are considered to be advisory – they contain much
material which is in contradiction with the first two, and with each other.
However, they have nonetheless influenced this document and deserve to be
acknowledged here.
MISRA-C:2004 has since been superseded by MISRA-C:2012, but no copy could
be located for review. However, it is known that MISRA-C:2012 extends support
to C99 – as such, for the purposes of this document the use of C99 features shall
not be considered to be contradictory to MISRA-C.
Appendix A: Coding Standard Christopher Kerr Chapter 1: Preamble
A-3
The format of this document, and some of its content, is adapted from the Barr
Group Embedded C Coding Standard. This usage is permitted, but requires the
inclusion of the following paragraph:
This document as well as the selection and arrangement of the rules it comprises is
Copyright © 2013 by Barr Group. It is permissible for individuals, companies, and
institutions to adapt all or a subset of the coding rules herein; indeed we hope that
many more will. This may be done simply by identifying the “Barr Group
Embedded C Coding Standard” as the source of your rules and retaining this
paragraph in its entirely. All other rights in copyright law are reserved by Barr
Group.
Appendix A: Coding Standard Christopher Kerr Chapter 2: General Rules
A-4
2 GENERAL RULES 2.1 LANGUAGE RULES:
a. All programs shall be written in ISO C, where ISO C is defined to be either:
i. The latest available ISO Standard for the C Programming Language (currently C99) OR
ii. The most recent ISO Standard for the C Programming Language supported by the compiler.
b. Appropriate compiler options are to be used to restrict the feature set to ISO C
c. The use of proprietary language extensions (including additional keywords and #pragma directives) is forbidden, with the following exceptions:
i. Modules which are platform-specific and require special features, e.g. to create an Interrupt Service Routine.
ii. Low-level initialisation code requiring linker directives, e.g. for placement of data at a specific location in memory.
iii. Inline assembler using the asm keyword (for GCC) or equivalent. Use of inline assembler should be minimised to maximise portability and readability.
RATIONALE: C99 brings valuable features to the C language, including:
iv. The ability to mix declarations and code, i.e. to declare a variable at any point in a function
v. The first expression in a for loop may be a declaration, as in C++
vi. The inline keyword, which hints to the compiler that a function should be included inline rather than called (i.e. to avoid function-calling overhead for a short functions)
vii. Boolean type _Bool, typedef’d as bool in <stdbool.h> with macros for true and false
viii. C++ style // one line comments
ix. More flexible initialisation of arrays and structs
Furthermore, C99 clarifies several points that were poorly defined in previous versions of ISO C:
i. Integer division and modulus operators always truncate towards 0
ii. Unspecified declarations no longer default to int e.g. the declaration f(); is illegal rather than being equivalent to int f();
The remainder of this document will assume that these features are available.
EXCEPTIONS: None. The only relevant objection (compiler support) is explicitly allowed in Rule 2.1a.ii
Appendix A: Coding Standard Christopher Kerr Chapter 2: General Rules
A-5
1.2 VERSION CONTROL RULES:
a. Use of a version control system such as Subversion is required.
RATIONALE:
The benefits of version-controlled source code are many. It gives programmers
the ability to:
i. Revert changes made in error
ii. Return to a known-working state
iii. Review the history of a piece of code
iv. Collaborate with other developers
v. Add new features without destabilising the existing codebase
Finally, and most prosaically, a remote version control system is an effective off-
site backup solution.
EXCEPTIONS:
None. Even projects with only a single developer benefit from the above-listed
features.
1.3 PARENTHESES RULES:
a. Be a parenthesis zealot. Do not rely on C’s operator precedence rules – use
parentheses to indicate intent and to ensure proper execution order. Consider
breaking long statements into multiple lines of code to aid readability.
b. Each operand of the logical operators II and && shall be surrounded by
parentheses, unless said operand is a single identifier or constant.
RATIONALE:
C’s operator precedence rules are unintuitive and difficult to remember. Relying
on these rules makes code harder to maintain – it is preferable that code
communicates intent clearly.
EXAMPLE: if ((depth > 0) && (depth < MAX_DEPTH))
fillFactor = (radius * depthCm) / (viscosity % SOME_CONST);
EXCEPTIONS:
None.
Appendix A: Coding Standard Christopher Kerr Chapter 2: General Rules
A-6
1.4 LINES RULES:
a. No line of code shall contain more than one statement.
i. Use of the comma , operator is forbidden except in the initialisation of a for
loop
b. No line of code shall exceed 80 characters in width.
c. Blank lines shall be used to separate natural blocks of code.
d. Each source file shall have a blank line at the end.
RATIONALE:
Lines which contain more than one statement allow some statements to be easily
overlooked. This is particularly the case for use of the comma , operator.
Separating code into natural blocks improves comprehension of code structure
and permits more natural documentation.
Some compilers require source files to end in a blank line. Comply with this for
portability.
EXCEPTIONS:
Legacy modules which breach these rules should be left as-is if the violation is
purely cosmetic, unless significant modification to these modules is otherwise
required. Rule 1.4a is not cosmetic and breaches should always be corrected.
Appendix A: Coding Standard Christopher Kerr Chapter 2: General Rules
A-7
1.5 BRACES RULES:
a. Braces shall surround blocks of code following if, else, switch, while, do, and for statements, including any single or empty statements.
i. An empty statement must include an explanatory comment.
b. Each left brace shall appear by itself on the line following the statement. The corresponding right brace shall appear on a line by itself following the code block, and should be aligned with its corresponding left brace.
RATIONALE:
A previous version of this document contained the following wording:
Braces should not be used to surround single or empty statements
If a single statement has an associated comment, and that comment is too long to
place to the right of the statement, the statement and its associated comment
should be treated as a block and surrounded with braces as per normal.
Although there is a risk associated with single statements which are not
surrounded by braces, this risk is considered to be small and outweighed by the
benefits of improved code density and readability.
In light of recent vulnerabilities, including the Apple “GotoFail” SSL bug and the
OpenSSL “Heartbleed” bug, this exception has been removed. The previous
rationale is no longer considered adequate.
EXAMPLE: bool example(void) for (int i = 0; i < 10; i++) if (doSomething() != ERROR_CATASTROPHIC) return true; else doSomethingElse();
return false;
EXCEPTIONS:
None.
Appendix A: Coding Standard Christopher Kerr Chapter 2: White Space
A-8
2 WHITE SPACE 2.1 INDENTATION RULES:
a. Each indentation level is to consist of 1 tab character
b. Indentation is to be increased by 1 level for code blocks inside braces, however:
i. Within a switch statement, each case statement should be at the same level
of indentation as the switch; the contents of each case should be indented
by 1 level.
c. Pairs of braces shall be aligned (i.e. indented to the same level)
d. Whenever a line of code is broken onto multiple lines for readability, indent the
second and any following lines in the most readable manner possible.
RATIONALE:
Indentation is required to maintain code readability, but indentation levels are a
personal preference. Consistent usage of tabs permits indentation levels to be set
to the programmer’s preference.
EXAMPLE: void example(uint8_t errCode)
// Switch statements per 2.1b.i
switch (errCode)
case ERROR_ONE:
doSomething();
break;
case ERROR_TWO:
doSomethingElse();
break;
default:
break;
// Multi-line indentation as per 2.1d
if (first_very_long_comparison_here
&& second_very_long_comparison_here
&& third_very_long_comparison_here)
// Single statement in braces as per 1.5a
doSomething();
Appendix A: Coding Standard Christopher Kerr Chapter 2: White Space
A-9
EXCEPTIONS:
Legacy modules which breach these rules should be left as-is unless significant
modification to these modules is otherwise required – altering indentation will
result in every line appearing modified to difference-tracking version control
systems. Should such an update be required, indentation should be changed in a
single distinct version control revision.
2.2 ALIGNMENT RULES:
a. Variable names within a block of related declarations shall have their first
characters aligned.
b. The names of struct and union members shall have their first characters aligned.
c. The assignment operators within a block of adjacent assignment statements shall
be aligned.
d. The # of a pre-processor directive shall always be located in column 1, except when
indenting within an #if or #ifdef sequence
RATIONALE:
Visual alignment should be used as a cue for similarity and relatedness.
EXCEPTIONS:
None.
Appendix A: Coding Standard Christopher Kerr Chapter 2: White Space
A-10
2.3 SPACES RULES:
a. The keywords if, else, while, for, switch, and return shall always be followed by
one space.
b. The following shall always be preceded and followed by one space:
i. The assignment operators =, +=, -=, *=, /=, %=, &=, |=, ^=, ~=, and !=
ii. The binary operators +, -, *, /, %, <, <=, >, >=, ==, !=, <<, >>, &, |, ^, &&, and ||
c. Each of the unary operators +, -, ++, --, !, and ~ shall always be written without a
space on the operand side, and with one space on the other side.
d. The pointer operator * shall:
i. Be written with a space on each side when used in declarations
ii. Otherwise be written without a space on the operand side
e. The “address of” operator & shall be written without a space on the operand side.
f. The ternary operator shall have each character ( ? and : ) preceded and followed
by one space.
g. The structure member and structure pointer operators ( . and -> ) shall always be
written without surrounding spaces.
h. The left and right brackets of the array subscript operator ( [ and ] ) shall always
be written without surrounding spaces.
i. Expressions within parentheses shall always have no spaces adjacent to the left
and right parenthesis characters.
j. The left and right parentheses of a function call shall always be without
surrounding spaces.
k. In function definitions, there shall be one space between the function name and
the left parenthesis of the argument list.
l. Each comma separating function parameters shall be followed by one space.
m. Each semicolon separating the elements of a for statements shall be followed by
one space.
n. Semicolons shall directly follow the statement they terminate, without a preceding
space.
RATIONALE:
These rules are given purely for consistency and readability. Consistent use of
whitespace makes code more readable and bugs easier to spot.
EXCEPTIONS:
None.
Appendix A: Coding Standard Christopher Kerr Chapter 3: Comments
A-11
3 COMMENTS 3.1 RESTRICTIONS ON USAGE RULES:
a. Both C style (i.e. /* ... */) and C++ style (single line preceded by //) comments
are acceptable.
b. Comments shall never be nested.
c. Comments shall never be used to disable a block of code.
i. Use of a C++ style // comment to temporarily disable a single line of code is
permitted, but must be accompanied by an explanatory comment prefixed
with TEMP:.
ii. For blocks of multiple lines, instead use preprocessor conditional
compilation (e.g. #if 0 … #endif). The block must be preceded by an
explanatory comment prefixed with TEMP:.
iii. No disabled code shall remain in the source code of a release or release
candidate. Note that this is distinct from other uses of conditional
compilation – code blocks which may sometimes be enabled (e.g. #ifdef
ENABLE_FEATURE ... #endif) are permitted.
RATIONALE:
Nested comments, specifically C-style comments nested within C-style
comments, are unsupported - the outer comment will be unintentionally closed
by the */ of the inner pair.
Given that comments will be used to document most code blocks, this behaviour
makes “commenting out” code blocks using C-style comments impossible. C++
style comments used for this purpose do not share the same fault, but prefixing
every line in a code block is time-consuming, error-prone and pointless given the
existence of a better solution.
EXCEPTIONS:
None.
Appendix A: Coding Standard Christopher Kerr Chapter 3: Comments
A-12
3.2 LOCATION AND CONTENT RULES:
a. All comments shall be written in English.
b. Comments should be written clearly and concisely, with correct spelling and
grammar.
c. Comments which document the purpose or function of a natural code block shall
be located directly above the relevant block, at the same level of indentation.
d. Comments which document a detail of a particular line shall be located to the right
of the line. All such comments within a code block shall be aligned at the same level
of indentation.
e. Assume that the reader knows the C programming language. Avoid documenting
the obvious.
f. Comments should cite sources for technical details, e.g. reference manual page
numbers.
g. All assumptions shall be documented in comments.
h. Each module and function shall be documented in a manner suitable for processing
by an automatic documentation generator such as Doxygen.
i. The following capitalised prefixes shall be used as appropriate:
i. WARNING: highlights sections of code with subtle dependencies which
require consideration before changes are made.
ii. NOTE: provides descriptive “why” comments, e.g. explaining a non-obvious
decision or an assumption made by the original programmer.
iii. TODO: indicates code still under construction, and explains what work
remains to be done.
iv. TEMP: marks code which has been temporarily disabled.
RATIONALE:
Clear commenting encourages clear thinking, which produces better code.
Use of Doxygen keeps documentation near the relevant code, increasing the
likelihood that it will remain relevant and correct.
Appendix A: Coding Standard Christopher Kerr Chapter 4: Modules
A-13
4 MODULES 4.1 MODULE NAMING CONVENTIONS RULES:
a. All module filenames shall consist only of lowercase letters, numbers and underscores.
i. No spaces shall ever appear within a filename.
b. Module names shall be unique within their first 8 characters.1
c. Source and header files shall have the extensions .c and .h respectively.
d. No module shall share the name of a C Standard Library header file.
e. Any module containing a main() function shall have the word “main” in its
filename.
RATIONALE:
Consistent use of lowercase file naming ensures cross-platform and cross-
toolchain compatibility – on Unix-like systems, sci.h and SCI.h are two different
files, but these names cannot be distinguished by Windows.
Rule 4.1e is intended to bring clarity to project structure. For example, given a
project Foo, both main.c and foo_main.c are permissible filenames for the module
which contains the function main().
EXCEPTIONS:
None.
1 C99 specifies (section 5.2.4.1) that the minimum supported “significant initial characters” for external identifiers be in excess of 31 characters. ANSI C (C89) required only 6 characters. Although the new limit is much higher, 8 characters has been chosen for readability.
Appendix A: Coding Standard Christopher Kerr Chapter 4: Modules
A-14
4.2 HEADER FILES RULES:
a. There shall always be precisely one header file for each source file, and both shall
always have the same root name.
b. Each header file shall contain a preprocessor #ifndef guard against multiple
inclusion.
c. The header file shall define only the external interface of a module.
i. Functions, data types and structures, constants, and macros internal to a
module should not be placed in the header file.
ii. No storage for any variable shall be declared in the header file.
iii. Use of internal datatypes outside of their module is strongly discouraged –
public interface functions should use only standard datatypes. Note that this
is not intended to forbid the use of enumerated types in public interfaces.
iv. A header may use the extern keyword to share a constant with another
module, and this is encouraged as the correct way to share constants when
necessary.
v. Use of the extern keyword within a header file to share a global variable
with other modules is strongly discouraged. When cross-module access to a
global variable is required, it is preferable to provide public “Get” and “Set”
functions. If direct access must be provided, the global variable shall be
declared volatile and protected from race conditions wherever it is used.
d. Headers should not include other headers.
i. An exception may be made for header files whose sole purpose is to collate
common includes to reduce code clutter. The number of such header files
should be small.
e. The prototype of an Interrupt Service Routine shall not be included in the public
interface presented by the module’s header file. ISR’s are special and should never
be called by other functions. See also Rule 4.3b.x.
Appendix A: Coding Standard Christopher Kerr Chapter 4: Modules
A-15
RATIONALE:
C defaults to giving variables and functions global scope. This increases coupling
between modules, which can result in unexpected and dangerous side effects.
Minimising scope reduces this unnecessary coupling.
EXAMPLE:
An appropriate preprocessor guard against multiple inclusion is shown below:
#ifndef _EXAMPLE_H
#define _EXAMPLE_H
...
#endif /* _EXAMPLE_H */
EXCEPTIONS:
None.
Appendix A: Coding Standard Christopher Kerr Chapter 4: Modules
A-16
4.3 SOURCE FILES RULES:
a. Each source file shall contain only functionality related to one logical module.
Examples of appropriate logical modules include peripheral drivers and
communication protocols.
i. It may be appropriate to divide complex modules into smaller sub-modules.
b. Each source file shall be comprised of some subset of the following list of sections,
ordered as listed. Each section shall be introduced by a comment.
i. Introductory comment block
ii. #include directives
iii. Datatype definitions
iv. Constant definitions
v. Macro definitions (but note the restrictions on macro use in 6.2g)
vi. Static data declarations (globals)
vii. Private function prototypes
viii. Public function bodies
ix. Private function bodies
x. Interrupt Service Routines
c. Each source file shall #include the header file of the same name if it exists.
d. #include directives shall always use relative paths.
e. Each source file shall include only header files which are used in that module.
f. No source file shall #include another source file.
RATIONALE:
Consistent internal layout makes the purpose and structure of modules clear to
the maintainer.
Rule 4.3c ensures that the compiler checks each public function for consistency
with its prototype.
Rule 4.3b.x is complementary to Rule 4.2e – by both hiding the ISR’s prototype
and placing the ISR at the end of the source file, no normal function can
inadvertently call the ISR.
EXCEPTIONS:
None.
Appendix A: Coding Standard Christopher Kerr Chapter 5: Data Types
A-17
5 DATA TYPES 5.1 NAMING CONVENTIONS RULES:
a. All new structures, unions, and enumerations shall be named as a type via typedef
b. All new types shall be named with the suffix _t
c. Type names shall begin with a lowercase letter and use camel case to separate
words (i.e. capitalising the first letter of each subsequent word)
i. An underscore may be used to separate words if required for
legibility.
d. Any datatype that forms part of a module’s public interface shall have its name
prefixed by the name of the module (with the first letter capitalised) followed by
an underscore.
RATIONALE:
Type names, variable names, and function names within a module may all be very
similar. Adding a suffix creates an appropriate distinct name.
EXAMPLE: // New type defined in header file: Part of public interface per
5.1d
typedef struct
uint8_t frequency;
uint16_t anotherVar;
Audio_dataExample_t
EXCEPTIONS:
It is not necessary or desirable to use typedef on structures and unions defined
within another structure or union. Note, however, that anonymous structs and
unions are not supported in ISO C versions prior to C11, so such structs and
unions must be named for portability.
Appendix A: Coding Standard Christopher Kerr Chapter 5: Data Types
A-18
5.2 INTEGER TYPES RULES:
a. Prefer the use of the following fixed-width types, supplied by <stdint.h>:
Width Signed Unsigned
8 bits int8_t uint8_t
16 bits int16_t uint16_t
32 bits int32_t uint32_t
64 bits int64_t uint64_t
b. The keywords short and long shall not be used.
c. Use of the char type shall be restricted to operations concerning strings.
d. None of the bit-wise operations shall be used to manipulate signed data.
e. Signed integers shall not be combined with unsigned integers in comparisons or
expressions.
i. In general, signed types should be considered the default choice for all
mathematical operations including loop counters.
RATIONALE:
The size of C standard types is implementation defined. C99 adds the <stdint.h>
header which provides cross-platform support for fixed-width types.
EXCEPTIONS:
If the compiler does not support C99, it is appropriate to create the necessary
typecasts to provide the expected fixed-width types based on char, short, int,
long, and long long
5.3 FLOATING POINT TYPES RULES:
a. Avoid the usage of floating point types wherever possible. Fixed-point math may be a useful alternative.
b. Never test for equality or inequality of any floating point value.
RATIONALE:
Many microcontrollers do not natively support floating point math, but silently
implement it by linking in a large and slow emulation library.
The nature of floating point math is such that values cannot accurately be
compared without first rounding to a known precision.
EXCEPTIONS:
None.
Appendix A: Coding Standard Christopher Kerr Chapter 6: Procedures and Functions
A-19
6 PROCEDURES AND FUNCTIONS 6.1 NAMING CONVENTIONS RULES:
a. No procedure shall have a name that is a keyword or base type in C or C++, including K&R C, C89, C99, C++98, C11 and C++11. Restricted names include
interrupt, inline, restrict, class, true, false, public, private, friend, and protected
b. No procedure shall have a name that overlaps with a procedure from the C
standard library, e.g. printf or strlen
c. No procedure shall have a name that begins with an underscore.
d. Each procedure’s name shall be descriptive of its function.
i. Appropriate names will often include verbs that describe what the function
does, e.g. Adc_Read()
ii. Alternatively, functions may be named based on the question they answer,
e.g. IsDataNew()
e. Capitalisation shall be used to separate words in procedure names.
i. An underscore may be used to aid readability, but only if the resulting name
is clearly not of the form used in rule 6.1g
f. Initial capitalisation shall be used to indicate scope:
i. Initial lowercase letters indicate a private function, e.g. doThis()
g. The names of all public functions shall be prefixed with the name of their module
followed by an underscore and an initial capital, e.g. Uart_StatusFlag
RATIONALE:
Naming rules exist to ensure consistent usage for clarity.
EXCEPTIONS:
None.
Appendix A: Coding Standard Christopher Kerr Chapter 6: Procedures and Functions
A-20
6.2 FUNCTIONS RULES:
a. A prototype shall be defined for each public function in the module header file.
b. A prototype shall be defined for each private function in the module code file. To
ensure consistency checking, the prototypes shall be placed before any public
functions, and the private functions shall be placed after any public functions.
c. All private functions shall be declared static
d. Each parameter shall be explicitly declared and meaningfully named.
e. Functions with a return value shall have at least one return statement located at
the end of the function.
i. Additional return statements are typically permissible, but note that they
are forbidden by several standards for safety-critical systems including
MISRA-C and IEC61508
f. Functions should be kept to a reasonable length – around 50-100 lines, or brief
enough to print on an A4 page.
g. Function-like macros shall not be used if an inline function can be used to the same
ends.
h. If a function-like macro must be used, its design is subject to the following rules:
i. Surround the entire macro body with parentheses.
ii. Surround each use of a parameter with parentheses.
iii. Use each argument only once, to avoid unintended side effects.
RATIONALE:
Producing code that uses neither break statements nor multiple exit points is
difficult, so this standard chooses to allow the lesser of two evils.
Function-like macros are largely unnecessary given the introduction of the inline
keyword in C99. If they must be used, it is important to avoid side effects such as
multiple incrementation.
EXCEPTIONS:
None.
Appendix A: Coding Standard Christopher Kerr Chapter 6: Procedures and Functions
A-21
6.3 INTERRUPT SERVICES ROUTINES RULES:
a. Interrupt service routines shall be named with the suffix _isr
b. To ensure ISR’s are not inadvertently called from other parts of the code, ISR’s shall
not have a prototype in either their module code file or their module header file,
and shall be placed at the end of the associated module.
c. If possible on the implementation architecture, all ISR’s should be declared static.
i. Note that this is not possible on architectures which make use of a vector
table of pointers to ordinary functions, such as ARM Cortex-M.
d. If required by the implementation architecture, ISR’s should be indicated to the
compiler using the appropriate #pragma or non-ANSI keyword.
e. A stub or default interrupt handler shall be installed in the vector table for all
unused interrupt sources.
RATIONALE:
ISR’s are not normal functions.
EXCEPTIONS:
None.
Appendix A: Coding Standard Christopher Kerr Chapter 7: Variables
A-22
7 VARIABLES 7.1 NAMING CONVENTIONS RULES:
a. No variable shall have a name that is a keyword or base type in C or C++, including K&R C, C89, C99, C++98, C11 and C++11. Restricted names include interrupt,
inline, restrict, class, true, false, public, private, friend, and protected
b. No variable shall have a name that overlaps with a variable from the C standard
library.
c. No variable shall have a name that begins with an underscore.
d. No variable name shall contain references to data subject to external change – for
example, the number of bits in the underlying type.
e. Each variable’s name shall be descriptive of its purpose.
i. Loop counters may make use of common single-letter abbreviations (i, j, k)
provided that these variables are declared and initialised in the loop control
expression, thus limiting their scope to the loop itself.
f. Capitalisation shall be used to separate words in variable names.
i. An underscore may be used to aid readability.
g. Initial capitalisation shall be used to indicate scope:
i. Initial lowercase letters indicate a local variable, e.g. someTempVar
ii. Initial uppercase letters indicate a variable with module scope, e.g. SomeModuleVar
h. The names of all constants shall be prefixed with k_
i. The names of all variables (including constants) visible outside their own module
shall be prefixed with the name of their module followed by an underscore, e.g. Uart_StatusFlag
i. Conversely, no variable of local or global scope may be named following this
pattern.
j. The names of all pointer variables shall be suffixed with Ptr. Pointers to pointers
shall be suffixed with PtPtr
RATIONALE:
Many compilers reserve names beginning with an underscore for internal use.
Other naming rules exist to ensure consistent usage for clarity.
EXCEPTIONS:
None.
Appendix A: Coding Standard Christopher Kerr Chapter 7: Variables
A-23
7.2 INITIALISATION RULES:
a. All variables shall be initialised before use.
b. Variables shall be created near to where they are used and with the minimum
possible scope.
RATIONALE:
C does not perform compile or run-time checking for initialised data, nor does the
specification call for declared variables to be automatically initialised to a known
value.
Initialisation of variables near where they are used aids readability. C99 permits
for variables to be created anywhere in a function, not just after an opening brace
as in C89;
EXCEPTIONS:
None.
Appendix A: Coding Standard Christopher Kerr Chapter 8: Expressions and Statements
A-24
8 EXPRESSIONS AND STATEMENTS 8.1 VARIABLE DECLARATIONS RULES:
a. Pointers and arrays shall be declared each on their own line.
i. If a pointer to a type has been named using typedef , multiple declaration of
the resulting type is permitted.
RATIONALE:
See the example – it is very easy in C to unintentionally declare only a single
variable as a pointer in a multiple declaration.
Declaration of multiple arrays is forbidden for readability.
EXAMPLE: // It is very easy to accidentally do this:
int * x, y, z; // Only x is a pointer
// This is correct, but forbidden by the rule for consistency and
readability:
int * x, * y, * z;
EXCEPTIONS:
None.
Appendix A: Coding Standard Christopher Kerr Chapter 8: Expressions and Statements
A-25
8.2 IF-ELSE STATEMENTS RULES:
a. The shortest (in lines of code) of the if and else if clauses should be placed first.
b. If-else statements shall not be nested deeper than two levels.
c. Assignments shall not be made within an if or else if expression.
d. Long switch statements should be avoided in favour of arrays of constants or
function pointers.
RATIONALE:
If-else structures should be formatted for readability. Complex nested structures
should be redesigned (e.g. as switch statements or function calls) to be easier to
follow, more readable, and more robust.
Assignments within an if expression are never necessary, and are nearly always
unintended errors. Intentional use is thus forbidden to aid code clarity and
automated code inspection.
EXCEPTIONS:
It may, in efficiency-driven applications, be necessary to reorder if statements to
ensure that the most critical case is executed fastest.
8.3 SWITCH STATEMENTS RULES:
a. All switch statements shall contain a default block.
b. When the break; is omitted from a case block to intentionally produce “fall-
through”, this shall be documented in a comment on the line where the break;
would normally appear.
c. The break; for each case shall be indented to the same level as the associated case
statement, rather than aligning with the contents of the enclosed case block.
d. Long switch statements should be avoided in favour of arrays of constants or
function pointers.
RATIONALE:
Bugs may be introduced by unhandled cases.
A missing break statement causes unintended fall-through to the following case.
This is a simple and common error, but the proposed alignment allows this error
to be easily spotted as an anomaly.
EXCEPTIONS:
None.
Appendix A: Coding Standard Christopher Kerr Chapter 8: Expressions and Statements
A-26
8.4 LOOPS RULES:
a. Constants shall be used (in preference to magic numbers) in the controlling
expressions of for and while loops.
b. Except for initialising the loop counter in the first clause of a for statement,
assignments shall not be made in a loop’s controlling expression.
c. Loops with an empty body (“busy wait” loops) shall include a comment explaining
their purpose.
d. Infinite loops shall be implemented using the expression for (;;) rather than while (1)
RATIONALE:
C does not range-check accesses to arrays. Loops which are not synchronised to
the size of the addressed data structure thus risk corrupting data outside of the
array. Such bugs can be prevented by use of a single constant for the array
declaration and its subsequent use in a loop controlling expression. Note that C99
does not permit const integers to be used as array dimensions, so this will require
the use of a #define.
Assignments in a loop’s controlling expression (except for the counter
initialisation) are never necessary, and create confusion regarding when the
expression will be evaluated. Include them inside the loop instead.
Regarding rule 8.4d: for (;;) and while (1) are technically equivalent, but the
latter includes a condition which always evaluates as true. Some compilers will
flag this with a warning, so the former style is preferred.
EXCEPTIONS:
It is frequently appropriate to start a loop with the counter initialised to zero, and
it is similarly appropriate to use zero as the end condition of a loop which counts
down (e.g. when iterating towards the base of an array). Used in these contexts,
the integer value 0 shall not be considered a magic number.
Appendix A: Coding Standard Christopher Kerr Chapter 8: Expressions and Statements
A-27
8.5 UNCONDITIONAL JUMPS RULES:
a. Unconditional jumps shall not be used:
i. All use of the goto keyword is forbidden.
ii. All use of the continue keyword is forbidden.
iii. Use of the break keyword outside of switch statement is forbidden.
RATIONALE:
Use of unconditional jumps is associated with “spaghetti code” – that is, code
which is difficult to follow and test. These language structures are unnecessary
and potentially dangerous.
EXCEPTIONS:
Use of the break keyword within switch statements is permitted.
Appendix A: Coding Standard Christopher Kerr Chapter 8: Expressions and Statements
A-28
8.6 EQUIVALENCE TESTS RULES:
a. When testing for equivalence between a variable and a constant value, always
place the constant on the left side of the comparison ( == ) operator.
i. For visual consistency, this rule should also be followed when testing
inequality ( != ).
RATIONALE:
Assignment instead of comparison is an extremely simple and common error.
Adherence to this rule permits errors to be detected at compile-time.
EXAMPLE: // Testing for equivalence:
const uint8_t someConstant = 5;
uint8_t testVar = 5;
if (k_someConstant == testVar)
// This is the correct method
if (testVar == k_someConstant)
// rule breach, because of the following possibility:
if (testVar = k_someConstant)
// Assignment in condition will always evaluate as true,
// but is perfectly legal C
if (k_someConstant = testVar)
// Following the rule prevents this issue, because
// assignment to a constant will cause a compiler error
EXCEPTIONS:
None.