RL ARM Building Apps With
User Manual: keil -
Open the PDF directly: View PDF
.
Page Count: 156
| Download | |
| Open PDF In Browser | View PDF |
Europe:
Bretonischer Ring 16
85630 Grasbrunn
Germany
Phone +49 89 / 45 60 40 - 20
FAX +49 89 / 46 81 62
United States:
1501 10th Street, Suite 110
Plano, Texas 75074
USA
Phone +1 972 312 1107
FAX +1 972 312 1159
Getting Started
Getting Started
Building Applications with RL-ARM
Building Applications with RL-ARM
For ARM Processor-Based Microcontrollers
www.keil.com
www.keil.com
2
Preface
Information in this document is subject to change without notice and does not
represent a commitment on the part of the manufacturer. The software described
in this document is furnished under license agreement or nondisclosure
agreement and may be used or copied only in accordance with the terms of the
agreement. It is against the law to copy the software on any medium except as
specifically allowed in the license or nondisclosure agreement. The purchaser
may make one copy of the software for backup purposes. No part of this manual
may be reproduced or transmitted in any form or by any means, electronic or
mechanical, including photocopying, recording, or information storage and
retrieval systems, for any purpose other than for the purchaser’s personal use,
without written permission.
Copyright © 1997-2009 ARM Ltd and ARM Germany GmbH.
All rights reserved.
Keil, the Keil Software Logo, µVision, MDK-ARM, RL-ARM, ULINK, and
Device Database are trademarks or registered trademarks of ARM Ltd, and
ARM Inc.
Microsoft® and Windows™ are trademarks or registered trademarks of Microsoft
Corporation.
NOTE
This manual assumes that you are familiar with Microsoft® Windows™ and the
hardware and instruction set of the ARM7™ and ARM9™ processor families or
the Cortex™-M series processors. In addition, basic knowledge of µVision®4 is
anticipated.
Every effort was made to ensure accuracy in this manual and to give appropriate
credit to persons, companies, and trademarks referenced herein.
Getting Started: Building Applications with RL-ARM
Preface
This manual is an introduction to the Real-Time Library (RL-ARM™), which is
a group of tightly coupled libraries designed to solve the real-time and
communication challenges of embedded systems based on ARM processor-based
microcontroller devices.
Using This Book
This book comes with a number of practical exercises that demonstrate the key
operating principles of the RL-ARM. To use the exercises you will need to have
both the Keil™ Microcontroller Development Kit (MDK-ARM™) installed and
the Real-Time Library (RL-ARM). If you are new to the MDK-ARM, there is a
separate Getting Started guide, which will introduce you to the key features. The
online documentation for the MDK-ARM, including the Getting Started guide, is
located at www.keil.com/support/man_arm.htm.
Alongside the standard RL-ARM examples, this book includes a number of
additional examples. These examples present the key principles outlined in this
book using the minimal amount of code. Each example is designed to be built
with the evaluation version of the MDK-ARM. If this is not possible, the
example is prebuilt so that it can be downloaded and run on a suitable evaluation
board.
This book is useful for students, beginners, advanced and experienced developers
alike.
However, it is assumed that you have a basic knowledge of how to use
microcontrollers and that you are familiar with the instruction set of your
preferred microcontroller. In addition, it is helpful to have basic knowledge on
how to use the µVision Debugger & IDE.
3
4
Preface
Chapter Overview
“Chapter 1. Introduction”, provides a product overview, remarks referring to
the installation requirements, and shows how to get support from the Keil
technical support team.
“Chapter 2. Developing with an RTOS”, describes the advantages of the RTX,
explains the RTX kernel, and addresses RTOS features, such as tasks,
semaphores, mutexes, time management, and priority schemes.
“Chapter 3. RL-Flash Introduction”, describes the features of the embedded
file system, how to set it up, configuration options, standard routines used to
maintain the file system, and how to adapt flash algorithms.
“Chapter 4. RL-TCPnet Introduction”, describes the network model, TCP key
features, communication protocols, and how to configure an ARM processorbased microcontroller to function with HTTP, Telnet, FTP, SMTP, or DNS
applications.
“Chapter 5. RL-USB Introduction”, describes the USB key features, the
physical and logical network, pipes and endpoints, the device communication
descriptors, and the supported interfaces and their classes.
“Chapter 6. RL-CAN Introduction”, describes the CAN key concepts, the
message frame, and the programming API implemented.
Getting Started: Building Applications with RL-ARM
5
Document Conventions
Examples
README.TXT
Description
1
Bold capital text is used to highlight the names of executable programs,
data files, source files, environment variables, and commands that you
can enter at the command prompt. This text usually represents
commands that you must type in literally. For example:
ARMCC.EXE
1
DIR
LX51.EXE
Courier
Text in this typeface is used to represent information that is displayed on
the screen or is printed out on the printer
This typeface is also used within the text when discussing or describing
command line items.
Variables
Text in italics represents required information that you must provide. For
example, projectfile in a syntax string means that you must supply the
actual project file name
Occasionally, italics are used to emphasize words in the text.
Elements that repeat…
Ellipses (…) are used to indicate an item that may be repeated
Omitted code
.
.
.
Vertical ellipses are used in source code listings to indicate that a
fragment of the program has been omitted. For example:
void main (void) {
.
.
.
while (1);
«Optional Items»
Double brackets indicate optional items in command lines and input
fields. For example:
C51 TEST.C PRINT «filename»
{ opt1 | opt2 }
Text contained within braces, separated by a vertical bar represents a
selection of items. The braces enclose all of the choices and the vertical
bars separate the choices. Exactly one item in the list must be selected.
Keys
Text in this sans serif typeface represents actual keys on the keyboard.
For example, “Press F1 for help”.
Underlined text
Text that is underlined highlights web pages. In some cases, it marks
email addresses.
It is not required to enter commands using all capital letters.
6
Content
Content
Preface................................................................................................................... 3
Document Conventions........................................................................................ 5
Content .................................................................................................................. 6
Chapter 1. Introduction.................................................................................... 10
RL-ARM Overview ......................................................................................... 10
RTX RTOS ...................................................................................................... 11
Flash File System ............................................................................................. 11
TCP/IP ............................................................................................................. 12
USB.................................................................................................................. 12
CAN ................................................................................................................. 13
Installation ....................................................................................................... 14
Product Folder Structure .................................................................................. 14
Last-Minute Changes ....................................................................................... 15
Requesting Assistance ..................................................................................... 15
Chapter 2. Developing With an RTOS ........................................................... 16
Getting Started ................................................................................................. 16
Setting-Up a Project ......................................................................................... 17
RTX Kernel ..................................................................................................... 19
Tasks ................................................................................................................ 19
Starting RTX .................................................................................................... 21
Creating Tasks ................................................................................................. 22
Task Management ............................................................................................ 24
Multiple Instances ............................................................................................ 24
Time Management ........................................................................................... 24
Time Delay ...................................................................................................... 25
Periodic Task Execution .................................................................................. 26
Virtual Timer ................................................................................................... 26
Idle Demon ...................................................................................................... 27
Inter-Task Communication .............................................................................. 28
Events .............................................................................................................. 28
RTOS Interrupt Handling ................................................................................ 29
Task Priority Scheme ....................................................................................... 31
Semaphores ...................................................................................................... 32
Using Semaphores ........................................................................................... 34
Signaling .......................................................................................................... 34
Multiplex.......................................................................................................... 34
Getting Started: Building Applications with RL-ARM
Rendezvous ...................................................................................................... 35
Barrier Turnstile............................................................................................... 36
Semaphore Caveats .......................................................................................... 38
Mutex ............................................................................................................... 38
Mutex Caveats ................................................................................................. 39
Mailbox ............................................................................................................ 39
Task Lock and Unlock ..................................................................................... 43
Configuration ................................................................................................... 43
Task Definitions............................................................................................... 44
System Timer Configuration ........................................................................... 45
Round Robin Task Switching .......................................................................... 45
Scheduling Options .......................................................................................... 45
Pre-emptive Scheduling ................................................................................... 46
Round Robin Scheduling ................................................................................. 46
Round Robin Pre-emptive Scheduling ............................................................ 47
Co-operative Multitasking ............................................................................... 47
Priority Inversion ............................................................................................. 47
Chapter 3. RL-Flash Introduction .................................................................. 49
Getting Started ................................................................................................. 49
Setting-Up the File System .............................................................................. 50
File I/O Routines.............................................................................................. 52
Volume Maintenance Routines ........................................................................ 54
Flash Drive Configuration ............................................................................... 56
Adapting Flash Algorithms for RL-Flash ........................................................ 58
MultiMedia Cards ............................................................................................ 60
Serial Flash ...................................................................................................... 62
Chapter 4. RL-TCPnet Introduction .............................................................. 63
TCP/IP – Key Concepts ................................................................................... 63
Network Model ................................................................................................ 63
Ethernet and IEEE 802.3 ................................................................................. 65
TCP/IP Datagrams ........................................................................................... 65
Internet Protocol .............................................................................................. 65
Address Resolution Protocol ........................................................................... 66
Subnet Mask .................................................................................................... 67
Dynamic Host Control Protocol DHCP ........................................................... 68
Internet Control Message Protocol .................................................................. 68
Transmission Control Protocol ........................................................................ 69
User Datagram Protocol................................................................................... 70
Sockets ............................................................................................................. 70
First Project - ICMP PING .............................................................................. 71
7
8
Content
Debug Support ................................................................................................. 74
Using RL-TCPnet with RTX ........................................................................... 74
RL-TCPnet Applications ................................................................................. 76
Trivial File Transfer ......................................................................................... 76
Adding the TFTP Service ................................................................................ 76
HTTP Server .................................................................................................... 77
Web Server Content ......................................................................................... 78
Adding Web Pages........................................................................................... 78
Adding HTML as C Code ................................................................................ 79
Adding HTML with RL-Flash ......................................................................... 81
The Common Gateway Interface ..................................................................... 82
Dynamic HTML .............................................................................................. 82
Data Input Using Web Forms .......................................................................... 84
Using the POST Method .................................................................................. 84
Using the GET Method .................................................................................... 87
Using JavaScript .............................................................................................. 88
AJAX Support ................................................................................................. 90
Simple Mail Transfer Client ............................................................................ 94
Adding SMTP Support .................................................................................... 94
Sending a Fixed Email Message ...................................................................... 95
Dynamic Message ............................................................................................ 96
Telnet Server .................................................................................................... 98
Telnet Helper Functions................................................................................. 100
DNS Client..................................................................................................... 101
Socket Library ............................................................................................... 102
User Datagram Protocol (UDP) Communication .......................................... 103
Transmission Control Protocol (TCP) Communication................................. 105
Deployment.................................................................................................... 108
Serial Drivers ................................................................................................. 109
Chapter 5. RL-USB Introduction .................................................................. 111
The USB Protocol – Key Concepts ............................................................... 111
USB Physical Network .................................................................................. 111
Logical Network ............................................................................................ 112
USB Pipes And Endpoints ............................................................................. 113
Interrupt Pipe ................................................................................................. 115
Isochronous Pipe ............................................................................................ 115
Bulk Pipe ....................................................................................................... 115
Bandwidth Allocation .................................................................................... 116
Device Configuration ..................................................................................... 117
Device Descriptor .......................................................................................... 118
Configuration Descriptor ............................................................................... 119
Getting Started: Building Applications with RL-ARM
Interface Descriptor ....................................................................................... 120
Endpoint Descriptor ....................................................................................... 121
RL-USB ......................................................................................................... 122
RL-USB Driver Overview ............................................................................. 122
First USB Project ........................................................................................... 124
Configuration ................................................................................................. 124
Event Handlers............................................................................................... 125
USB Descriptors ............................................................................................ 126
Class Support ................................................................................................. 127
Human Interface Device ................................................................................ 128
HID Report Descriptors ................................................................................. 128
HID Client ..................................................................................................... 133
Enlarging the IN & OUT Endpoint Packet Sizes........................................... 134
Mass Storage .................................................................................................. 136
Audio Class .................................................................................................... 138
Composite Device .......................................................................................... 139
Compliance Testing ....................................................................................... 140
Chapter 6. RL-CAN Introduction ................................................................. 141
The CAN Protocol – Key Concepts ............................................................... 141
CAN Node Design ......................................................................................... 142
CAN Message Frames ................................................................................... 143
CAN Bus Arbitration ..................................................................................... 145
RL-CAN Driver ............................................................................................. 146
First Project .................................................................................................... 146
CAN Driver API ............................................................................................ 147
Basic Transmit and Receive .......................................................................... 148
Remote Request ............................................................................................. 149
Object Buffers ................................................................................................ 151
Glossary ............................................................................................................ 152
9
10
Chapter 1. Introduction
Chapter 1. Introduction
The last few years have seen an explosive growth in both the number and
complexity of ARM processor-based microcontrollers. This diverse base of
devices now offers the developer a suitable microcontroller for almost all
applications. However, this rise in sophisticated hardware also calls for more and
more complex software. With ever-shorter project deadlines, it is becoming just
about impossible to develop the software to drive these devices without the use
of third-party middleware.
RTOS and Middleware
Components
RTX RTOS Source Code
TCPnet Networking Suite
Flash File System
Examples and Templates
The Keil Real-Time Library (RL-ARM) is a
collection of easy-to-use middleware components
that are designed to work across many different
microcontrollers. This allows you to learn the
software once and then use it multiple times.
The RL-ARM middleware integrates into the
Keil Microcontroller Development Kit
(MDK-ARM).
USB Device Interface
These two development tools allow you to
rapidly develop sophisticated software
CAN Interface
applications across a vast range of ARM
processor-based microcontrollers. In this book,
we will look at each of the RL-ARM middleware components and see how to use
all the key features in typical applications.
RL-ARM Overview
The RL-ARM library consists of
five main components; a Flashbased file system, a TCP/IP
networking suite, drivers for
USB and CAN, and the RTX
Kernel. Each of the middleware
components is designed to be
used with the Keil RTX real-time operating system. However, with the
exception of the CAN driver, each component may be used without RTX.
Getting Started: Building Applications with RL-ARM
RTX RTOS
Traditionally developers of
small, embedded applications
have had to write virtually all the
code that runs on the
microcontroller. Typically, this
is in the form of interrupt
handlers with a main
background-scheduling loop. While there is nothing intrinsically wrong with
this, it does rather miss the last few decades of advancement in program structure
and design. Now, for the first time, with the introduction of 32-bit ARM
processor-based microcontrollers we have low-cost, high-performance devices
with increasingly large amounts of internal SRAM and Flash memory. This
makes it possible to use more advanced software development techniques.
Introducing a Real-Time Operating System (RTOS) or real-time executive into
your project development is an important step in the right direction. With an
RTOS, all the functional blocks of your design are developed as tasks, which are
then scheduled by RTX. This forces a detailed design analysis and consideration
of the final program structure at the beginning of the development. Each of the
program tasks can be developed, debugged, and tested in isolation before
integration into the full system. Each RTOS task is then easier to maintain,
document, and reuse. However, using an RTOS is only half the story.
Increasingly, customers want products that are more complex in shorter and
shorter time. While microcontrollers with suitable peripherals are available, the
challenge is to develop applications without spending months writing the lowlevel driver code.
Flash File System
The RL-Flash file system allows
you to place a PC-compatible file
system in any region of a
microcontroller’s memory. This
includes the on-chip and external
RAM and Flash memory, as well
as SPI based Flash memory and
SD/MMC memory cards.
11
12
Chapter 1. Introduction
The RL-Flash file system comes with all the driver support necessary, including
low-level Flash drivers, SPI drivers, and MultiMedia Card interface drivers. This
gets the file system up-and-running with minimal fuss and allows you to
concentrate on developing your application software. In the past, the use of a full
file system in a small, embedded microcontroller has been something of a luxury.
However, once you start developing embedded firmware with access to a small
file system, you will begin to wonder how you ever managed without it!
TCP/IP
The RL-TCPnet library is a full
networking suite written for
small ARM processor-based
microcontrollers specifically. It
consists of one generic library
with dedicated Ethernet drivers
for supported microcontrollers
and a single configuration file.
SLIP and PPP protocols are also
supported to allow UART-based
communication either directly from a PC or remotely via a modem.
The RL-TCPnet library supports raw TCP and UDP communication, which
allows you to design custom networking protocols. Additional application layer
support can be added to enable common services, including SMTP clients to send
email notification, plus DNS and DHCP clients for automatic configuration. RLTCPnet can also enable a microcontroller to be a server for the TELNET, HTTP,
and File Transfer (FTP) protocols.
USB
The USB protocol is complex
and wide-ranging. To implement
a USB-based peripheral, you
need a good understanding of the
USB peripheral, the USB
protocol, and the USB host
operating system.
Getting Started: Building Applications with RL-ARM
Typically, the host will be a PC. This means that you need to have a deep
knowledge of the Windows operating system and its device drivers. Getting all
of these elements working together would be a development project in its own.
Like the TCP/IP library, the RL-USB driver is a common software stack
designed to work across all supported microcontrollers. Although you can use
the RL-USB driver to communicate with a custom Windows device driver, it has
been designed to support common USB classes. Each USB class has its own
native driver within the Windows operating system. This means that you do not
need to develop or maintain your own driver.
The class support provided with RL-USB includes Human Interface Device
(HID), Mass Storage Class (MSC), Communication Device Class (CDC), and
Audio Class. The HID Class allows you to exchange custom control and
configuration data with your device. The Mass Storage Class allows the
Windows operating system to access the data stored within the RL-Flash file
system in the same manner as a USB pen drive. The Communication Device
Class can be used to realize a virtual COM Port. Finally, the Audio Class allows
you to exchange streaming audio data between the device and a PC. Together
these four classes provide versatile support for most USB design requirements.
CAN
The RL-CAN driver is the one
component of the RL-ARM
library that is tightly coupled to
the RTX. The CAN driver
consists of just six functions that
allow you to initialize a given
CAN peripheral, define, transmit
and receive CAN message
objects, and exchange data with other nodes on the CAN network.
The RL-CAN driver has a consistent programming API for all supported CAN
peripherals, allowing easy migration of code or integration of several different
microcontrollers into the one project. The CAN driver also uses RTX message
queues to buffer, transmit and receive messages, ensuring ordered handling of the
CAN network data.
13
14
Chapter 1. Introduction
Installation
The RL-ARM is a collection of middleware components designed to integrate
with the Keil Microcontroller Development Kit (MDK-ARM). To use this book
you will need to have both the MDK-ARM and RL-ARM installed on your PC.
MDK-ARM may be installed from either CD-ROM, or may be downloaded from
the web. Currently, RL-ARM may only be downloaded from the web.
Keil products are available on CD-ROM and via download from www.keil.com.
Updates to the related products are regularly available at www.keil.com/update.
Demo versions of various products are obtainable at www.keil.com/demo.
Additional information is provided under www.keil.com/arm.
Please check the minimum hardware and software requirements that must be
satisfied to ensure that your Keil development tools are installed and will
function properly. Before attempting installation, verify that you have:
A standard PC running Microsoft Windows XP, or Windows Vista,
1GB RAM and 500 MB of available hard-disk space is recommended,
1024x768 or higher screen resolution; a mouse or other pointing device,
A CD-ROM drive.
Product Folder Structure
The SETUP program copies the development tools into subfolders. The base
folder defaults to C:\KEIL. When the RL-ARM is installed, it integrates into the
MDK-ARM installation. The table below outlines the key RL-ARM files:
File Type
Path
MDK-ARM Toolset
C:\KEIL\ARM
Include and Header Files
C:\KEIL\ARM\RVxx\INC
Libraries
C:\KEIL\ARM\RVxx\LIB
Source Code
C:\KEIL\ARM\RL
Standard Examples
C:\KEIL\ARM\Boards\manufacturer\board
Flash Programming
C:\KEIL\ARM\FLASH
On-line Help Files and Release Notes
C:\KEIL\ARM\HLP
Getting Started: Building Applications with RL-ARM
Last-Minute Changes
As with any high-tech product, last minute changes might not be included into
the printed manuals. These last-minute changes and enhancements to the
software and manuals are listed in the Release Notes shipped with the product.
Requesting Assistance
At Keil, we are committed to providing you with the best-embedded
development tools, documentation, and support. If you have suggestions and
comments regarding any of our products, or you have discovered a problem with
the software, please report them to us, and where applicable make sure to:
1. Read the section in this manual that pertains to the task you are attempting,
2. Check the update section of the Keil web site to make sure you have the latest
software and utility version,
3. Isolate software problems by reducing your code to as few lines as possible.
If you are still having difficulties, please report them to our technical support
group. Make sure to include your license code and product version number
displayed through the Help – About Menu of µVision. In addition, we offer the
following support and information channels, accessible at ww.keil.com/support.
1. The Support Knowledgebase is updated daily and includes the latest questions
and answers from the support department,
2. The Application Notes can help you in mastering complex issues, like
interrupts and memory utilization,
3. Check the on-line Discussion Forum,
4. Request assistance through Contact Technical Support (web-based E-Mail),
5. Finally, you can reach the support department directly via
support.intl@keil.com or support.us@keil.com.
15
16
Chapter 2. Developing With an RTOS
Chapter 2. Developing With an RTOS
In the course of this chapter we will consider the idea of using RTX, the Keil
small footprint RTOS, on an ARM processor-based microcontroller. If you are
used to writing procedural-based C code on microcontrollers, you may doubt the
need for such an operating system. If you are not familiar with using an RTOS in
real-time embedded systems, you should read this chapter before dismissing the
idea. The use of an RTOS represents a more sophisticated design approach,
inherently fostering structured code development, which is enforced by the
RTOS Application Programming Interface (API).
The RTOS structure allows you to take an object-orientated design approach
while still programming in C. The RTOS also provides you with multithreaded
support on a small microcontroller. These two features create a shift in design
philosophy, moving us away from thinking about procedural C code and flow
charts. Instead, we consider the fundamental program tasks and the flow of data
between them. The use of an RTOS also has several additional benefits, which
may not be immediately obvious. Since an RTOS-based project is composed of
well-defined tasks, using an RTOS helps to improve project management, code
reuse, and software testing.
The tradeoff for this is that an RTOS has additional memory requirements and
increased interrupt latency. Typically, RTX requires between 500 Bytes and
5KBytes of RAM and 5KBytes of code, but remember that some of the RTOS
code would be replicated in your program anyway. We now have a generation of
small, low-cost microcontrollers that have enough on-chip memory and
processing power to support the use of an RTOS. Developing using this
approach is therefore much more accessible.
Getting Started
This chapter first looks at setting up an introductory RTOS project for ARM7,
ARM9, and Cortex-M based microcontrollers. Next, we will go through each of
the RTOS primitives and explain how they influence the design of our
application code. Finally, when we have a clear understanding of the RTOS
features, we will take a closer look at the RTOS configuration file.
Getting Started: Building Applications with RL-ARM
Setting-Up a Project
The first exercise in the examples accompanying this book provides a PDF
document giving a detailed step-by-step guide for setting up an RTX project.
Here we will look at the main differences between a standard C program and an
RTOS-based program. First, our µVision project is defined in the default way.
This means that we start a new project and select a microcontroller from the
µVision Device Database®. This will add the startup code and configure the
compiler, linker, simulation model,
debugger, and Flash programming
algorithms. Next, we add an empty C
module and save it as main.c to start a Cbased application. This will give us a
project structure similar to that shown
on the right. A minimal application
program consists of an Assembler file
for the startup code and a C module.
The RTX configuration is held in the
file RTX_Config.c that must be added to
your project. As its name implies,
RTX_Config.c holds the configuration
settings for RTX. This file is specific to
the ARM processor-based
microcontroller you are using. Different versions of the file are located in
C:\KEIL\ARM\STARTUP.
If you are using an ARM7 or ARM9-based microcontroller, you can select the
correct version for the microcontroller family you are using and RTX will work
“out-of-the-box”. For Cortex-M-based microcontrollers there is one generic
configuration file. We will examine this file in more detail later, after we have
looked more closely at RTX and understood what needs to be configured.
To enable our C code to access the RTX API, we need to add an include file to
all our application files that use RTX functions. To do this you must add the
following include file in main.
#include
17
18
Chapter 2. Developing With an RTOS
We must let the µVision IDE utility
know that we are using RTX so that it
can link in the correct library. This is
done by selecting “RTX Kernel” in the
Options for Target menu, obtained by
right clicking on “RTOS”.
The RTX Kernel library is added to the
project by selecting the operating
system in the dialog Options for Target.
When using RTX with an ARM7 or ARM9 based microcontroller, calls to the
RTOS are made by Software Interrupt instructions (SWI). In the default startup
code, the SWI interrupt vector jumps to a tight loop, which traps SWI calls. To
configure the startup code to work with RTX we must modify the SWI vector
code to call RTX.
A part of RTX runs in the privileged supervisor mode and is called with software
interrupts (SWI). We must therefore disable the SWI trap in the startup code.
With Cortex-based microcontroller, the interrupt structure is different and does
not require you to change the startup code, so you can ignore this step.
You must disable the default SWI handler and import the SWI_Handler used by
the RTOS, when used with ARM7 or ARM9.
Undef_Handler
;SWI_Handler
PAbt_Handler
DAbt_Handler
IRQ_Handler
FIQ_Handler
IMPORT
SWI_Handler
B
B
B
B
B
B
Undef_Handler
SWI_Handler
PAbt_Handler
DAbt_Handler
IRQ_Handler
FIQ_Handler
; Part of RTL
In the vector table, the default SWI_Handler must be commented out and the
SWI_Handler label must be declared as an import. Now, when RTX generates a
software interrupt instruction, the program will jump to the SWI_Handler in the
RTX library. These few steps are all that are required to configure a project to
use RTX.
Exercise: First Project
The first RTOS exercise guides you through setting up and debugging an RTXbased project.
Getting Started: Building Applications with RL-ARM
19
RTX Kernel
RTX consists of a scheduler that supports round-robin, pre-emptive, and cooperative multitasking of program tasks, as well as time and memory
management services. Inter-task communication is supported by additional
RTOS objects, including event triggering, semaphores, Mutex, and a mailbox
system. As we will see, interrupt handling can also be accomplished by
prioritized tasks, which are scheduled by the RTX kernel.
The RTX kernel contains a
scheduler that runs program code
as tasks. Communication
between tasks is accomplished
by RTOS objects such as events,
semaphores, Mutexes, and
mailboxes. Additional RTOS
services include time and
memory management and
interrupt support.
Tasks
The building blocks of a typical C program are functions that we call to perform
a specific procedure and which then return to the calling function. In an RTOS,
the basic unit of execution is a “Task”. A task is very similar to a C procedure,
but has some fundamental differences.
Procedure
unsigned int procedure (void)
…
…
return (val);
}
Task
{
__task void task (void)
for (;;) {
…
}
}
{
We always expect to return from C functions, however, once started an RTOS
task must contain an endless loop, so that it never terminates and thus runs
forever. You can think of a task as a mini self-contained program that runs
within the RTOS. While each task runs in an endless loop, the task itself may be
started by other tasks and stopped by itself or other tasks. A task is declared as a
C function, however RTX provides an additional keyword __task that should be
added to the function prototype as shown above. This keyword tells the compiler
20
Chapter 2. Developing With an RTOS
not to add the function entry and exit code. This code would normally manage
the native stack. Since the RTX scheduler handles this function, we can safely
remove this code. This saves both code and data memory and increases the
overall performance of the final application.
An RTOS-based program is made up of a number of tasks, which are controlled
by the RTOS scheduler. This scheduler is essentially a timer interrupt that allots
a certain amount of execution time to each task. So task1 may run for 100ms
then be de-scheduled to allow task2 to run for a similar period; task 2 will give
way to task3, and finally control passes back to task1. By allocating these slices
of runtime to each task in a round-robin fashion, we get the appearance of all
three tasks running in parallel to each other.
Conceptually we can think of each task as performing a specific functional unit
of our program, with all tasks running simultaneously. This leads us to a more
object-orientated design, where each functional block can be coded and tested in
isolation and then integrated into a fully running program. This not only imposes
a structure on the design of our final application but also aids debugging, as a
particular bug can be easily isolated to a specific task. It also aids code reuse in
later projects. When a task is created, it is allocated its own task ID. This is a
variable, which acts as a handle for each task and is used when we want to
manage the activity of the task.
OS_TID id1, id2, id3;
In order to make the task-switching process happen, we have the code overhead
of the RTOS and we have to dedicate a CPU hardware timer to provide the
RTOS time reference. For ARM7 and ARM9 this must be a timer provided by
the microcontroller peripherals. In a Cortex-M microcontroller, RTX will use the
SysTick timer within the Cortex-M processor. Each time we switch running
tasks the RTOS saves the state of all the task variables to a task stack and stores
the runtime information about a
Task Control Block
Task Stack
task in a Task Control Block. The
“context switch time”, that is, the
Priority & State
Context
time to save the current task state
and load up and start the next task,
is a crucial value and will depend
on both the RTOS kernel and the
Task
design of the underlying hardware.
Getting Started: Building Applications with RL-ARM
Each task has its own stack for saving its data during a context switch. The Task
Control Block is used by the kernel to manage the active tasks.
The Task Control Block contains information about the status of a task. Part of
this information is its run state. A task can be in one of four basic states,
RUNNING, READY, WAITING, or INACTIVE. In a given system only one
task can be running, that is, the CPU is executing its instructions while all the
other tasks are suspended in one of the other states. RTX has various methods of
inter-task communication: events, semaphores, and messages. Here, a task may
be suspended to wait to be signaled by another task before it resumes its READY
state, at which point it can be placed into RUNNING state by the RTX scheduler.
At any moment a single task may be running. Tasks may also be waiting on an
OS event. When this occurs, the tasks return to the READY state and are
scheduled by the kernel.
Task
Description
RUNNING
The currently running TASK
READY
TASKS ready to run
WAIT DELAY
TASKS halted with a time DELAY
WAIT INT
TASKS scheduled to run periodically
WAIT OR
TASKS waiting an event flag to be set
WAIT AND
TASKS waiting for a group event flag to be set
WAIT SEM
TASKS waiting for a SEMAPHORE
WAIT MUT
TASKS waiting for a SEMAPHORE MUTEX
WAIT MBX
TASKS waiting for a MAILBOX MESSAGE
INACTIVE
A TASK not started or detected
Starting RTX
To build a simple RTX-based program, we declare each task as a standard C
function and a TASK ID variable for each Task.
__task
__task
OS_TID
void task1 (void);
void task2 (void);
tskID1, tskID2;
After reset, the microcontroller enters the application through the main()
function, where it executes any initializing C code before calling the first RTX
function to start the operating system running.
21
22
Chapter 2. Developing With an RTOS
void main (void)
{
IODIR1 = 0x00FF0000;
os_sys_init (task1);
// Do any C code you want
// Start the RTX call the first task
}
The os_sys_init () function launches RTX, but only starts the first task running.
After the operating system has been initialized, control will be passed to this task.
When the first task is created it is assigned a default priority. If there are a
number of tasks ready to run and they all have the same priority, they will be
allotted run time in a round-robin fashion. However, if a task with a higher
priority becomes ready to run, the RTX scheduler will de-schedule the currently
running task and start the high priority task running. This is called pre-emptive
priority-based scheduling. When assigning priorities you have to be careful,
because the high priority task will continue to run until it enters a WAITING
state or until a task of equal or higher priority is ready to run.
Tasks of equal priority will be
scheduled in a round-robin
fashion. High priority tasks will
pre-empt low priority tasks and
enter the RUNNING state “on
demand”.
Two additional calls are
available to start RTX;
os_sys_init_prio(task1) will start the RTOS and create the task with a userdefined priority. The second OS call is os_sys_init_user(task1, &stack,
Stack_Size). This starts the RTOS and defines a user stack.
Creating Tasks
Once RTX has been started, the first task created is used to start additional tasks
required for the application. While the first task may continue to run, it is good
programming style for this task to create the necessary additional tasks and then
delete itself.
__task void task1 (void) {
tskID2 = os_tsk_create (task2,0x10);
tskID3 = os_tsk_create (task3,0x10);
os_tsk_delete_self ();
}
//
//
//
//
//
Create the second task
and assign its priority.
Create additional tasks
and assign priorities.
End and self-delete this task
Getting Started: Building Applications with RL-ARM
The first task can create further active tasks with the os_tsk_create() function.
This launches the task and assigns its task ID number and priority. In the
example above we have two running tasks, task2 and task3, of the same priority,
which will both be allocated an equal share of CPU runtime. While the
os_tsk_create() call is suitable for creating most tasks, there are some additional
task creation calls for special cases.
It is possible to create a task and pass a parameter to the task on startup. Since
tasks can be created at any time while RTX is running, a task can be created in
response to a system event and a particular parameter can be initialized on
startup.
tskID3 = os_tsk_create_ex (Task3, priority, parameter);
When each task is created, it is also assigned its own stack for storing data during
the context switch. This task stack is a fixed block of RAM, which holds all the
task variables. The task stacks are defined when the application is built, so the
overall RAM requirement is well defined. Ideally, we need to keep this as small
as possible to minimize the amount of RAM used by the application. However,
some tasks may have a large buffer, requiring a much larger stack space than
other tasks in the system. For these tasks, we can declare a larger task stack,
rather than increase the default stack size.
static U64 stk4 [400/8];
A task can now be declared with a custom stack size by using the
os_tsk_create_user() call and the dedicated stack.
tskID4 = os_tsk_create_user (Task4, priority, &stk4, sizeof (stk4));
Finally, there is a combination of both of the above task-creating calls where we
can create a task with a large stack space and pass a parameter on startup.
static U64 stk5 [400/8];
tskID5 = os_tsk_create_user_ex (Tsk5, prio, &stk5, sizeof (stk5), param);
Exercise: Tasks
This exercise presents the minimal code to start the RTOS and create two
running tasks.
23
24
Chapter 2. Developing With an RTOS
Task Management
Once the tasks are running, there are a small number of RTX system calls, which
are used to manage the running tasks. It is possible to elevate or lower a task’s
priority either from another function or from within its own code.
OS_RESULT os_tsk_prio (tskID2, priority);
OS_RESULT os_tsk_prio_self (priority);
As well as creating tasks, it is also possible for a task to delete itself or another
active task from the RTOS. Again we use the task ID rather than the function
name of the task.
OS_RESULT = os_tsk_delete (tskID1);
os_tsk_delete_self ();
Finally, there is a special case of task switching where the running task passes
control to the next ready task of the same priority. This is used to implement a
third form of scheduling called co-operative task switching.
os_tsk_pass ();
// switch to next ready to run task
Multiple Instances
One of the interesting possibilities of an RTOS is that you can create multiple
running instances of the same base task code. For example, you could write a
task to control a UART and then create two running instances of the same task
code. Here each instance of UART_Task would manage a different UART.
tskID3_0 = os_tsk_create_ex (UART_Task, priority, UART1);
Exercise: Multiple instances
This exercise creates multiple instances of one base task and passes a parameter
on startup to control the functionality of each instance.
Time Management
As well as running your application code as tasks, RTX also provides some
timing services, which can be accessed through RTX function calls.
Getting Started: Building Applications with RL-ARM
Time Delay
The most basic of these timing services is a simple timer delay function. This is
an easy way of providing timing delays within your application. Although the
RTX kernel size is quoted as 5K bytes, features such as delay loops and simple
scheduling loops are often part of a non-RTOS application and would consume
code bytes anyway, so the overhead of the RTOS can be less than it initially
appears.
void os_dly_wait (unsigned short delay_time)
This call will place the calling task into the WAIT_DELAY state for the
specified number of system timer ticks. The scheduler will pass execution to the
next task in the READY state.
During their lifetime, tasks move
through many states. Here, a
running task is blocked by an
os_dly_wait() call so it enters a
WAIT state. When the delay
expires, it moves to the READY
state. The scheduler will place it
in the RUN state. If its time slice
expires, it will move back to the
READY state.
When the timer expires, the task will leave the WAIT_DELAY state and move to
the READY state. The task will resume running when the scheduler moves it to
the RUNNING state. If the task then continues executing without any further
blocking OS calls, it will be de-scheduled at the end of its time slice and be
placed in the READY state, assuming another task of the same priority is ready
to run.
Exercise: Time Management
This exercise replaces the user delay loops with the OS delay function.
25
26
Chapter 2. Developing With an RTOS
Periodic Task Execution
We have seen that the scheduler runs tasks with a round-robin or pre-emptive
scheduling scheme. With the timing services, it is also possible to run a selected
task at specific time intervals. Within a task, we can define a periodic wake-up
interval.
void os_itv_set (unsigned short interval_time)
Then we can put the task to sleep and wait for the interval to expire. This places
the task into the WAIT_INT state.
void os_itv_wait (void)
When the interval expires, the task moves from the WAIT_INT to the READY
state and will be placed into the RUNNING state by the scheduler.
Exercise: Interval
This exercise modifies the two-task example to use interval service so that both
tasks run at a fixed period.
Virtual Timer
As well as running tasks on a defined periodic basis, we can define any number
of virtual timers, which act as countdown timers. When they expire, they run a
user call-back function to perform a specific action. A virtual timer is created
with the os_timer_create() function. This system call specifies the number of
RTOS system timer ticks before it expires and a value “info”, which is passed to
the callback function to identify the timer. Each virtual timer is also allocated an
OS_ID handle, so that it can be managed by other system calls.
OS_ID os_tmr_create (unsigned short tcnt, unsigned short info)
When the timer expires, it calls the function os_tmr_call(). The prototype for
this function is located in the RTX_Config.c file.
Getting Started: Building Applications with RL-ARM
void os_tmr_call (U16 info)
switch (info)
case 0x01:
…
break ;
}
{
{
// user code here
}
This function knows which timer has expired by reading the info parameter. We
can then run the appropriate code after the “case” statement.
Exercise: Timer
This exercise modifies the two-task-program to use virtual timers to control the
rate at which the LEDs flash.
Idle Demon
The final timer service provided by RTX is not really a timer, but this is probably
the best place to discuss it. If, during our RTOS program, there is no task
running and no task ready to run (e.g. they are all waiting on delay functions),
then RTX uses the spare runtime to call an “Idle Demon” that is located in the
RTX_Config.c file. This idle code is in effect a low priority task within the RTOS,
which only runs when nothing else is ready.
__task void os_idle_demon (void)
for (;;)
…
}
{
{
// user code here
}
You can add any code to this task, but it has to obey the same rules as user tasks.
Exercise: Idle Demon
This example demonstrates how to add code to the idle task, so that the
application can perform “book keeping” tasks in the spare cycles not consumed
by the main application.
27
28
Chapter 2. Developing With an RTOS
Inter-Task Communication
So far we have seen how application code can be written as independent tasks
and how we can access the timing services provided by RTX. In a real
application, we need to be able to communicate between tasks in order to make
an application useful. To enable this, a typical RTOS supports several different
communication objects, which can be used to link the tasks together to form a
meaningful program. RTX supports inter-task communication with events,
semaphores, mutexes, and mailboxes.
Events
When each task is first created, it has sixteen event flags. These are stored in the
Task Control Block. It is possible to halt the execution of a task until a particular
event flag or group of event flags are set by another task in the system.
Each task has 16 event flags. A
task may be placed into a waiting
state until a pattern of flags is set
by another task. When this
happens, it will return to the
READY state and wait to be
scheduled by the kernel.
The two event wait system calls
suspend execution of the task
and place it into the WAIT_EVNT state. By using the AND or OR version of the
event wait call, we can wait for a group of event flags to be set or until one flag
in a selected group is set. It is also possible to define a periodic timeout after
which the waiting task will move back to the READY state, so that it can resume
execution when selected by the scheduler. A value of 0xFFFF defines an infinite
timeout period.
OS_RESULT os_evt_wait_and (unsigned short wait_flags,
unsigned short timeout);
OS_RESULT os_evt_wait_or (unsigned short wait_flags,
unsigned short timeout);
Getting Started: Building Applications with RL-ARM
Any task can set the event flags of any other task in a system with the
os_evt_set() RTX function call. We use the task ID to select the task.
void os_evt_set (unsigned short event_flags, OS_TID task);
As well as setting a task’s event flags, it is also possible to clear selected flags.
void os_evt_clr (U16 clear_flags, OS_TID task);
When a task resumes execution after it has been waiting for an os_evt_wait_or()
function to complete, it may need to determine which event flag has been set.
The os_evt_get() function allows you to determine the event flag that was set.
You can then execute the correct code for this condition.
which_flag = os_evt_get ();
Exercise: Events
This exercise extends the simple two-task-example and uses event flags to
synchronize the activity between the active tasks.
RTOS Interrupt Handling
The use of event flags is a simple and efficient method of controlling actions
between tasks. Event flags are also an important method of triggering tasks to
respond to interrupt sources within the ARM processor-based microcontroller.
While it is possible to run C code in an interrupt service routine (ISR), this is not
desirable within an RTX-based application. This is because on an ARM7/9
based device you will disable further general-purpose interrupts until you quit the
ISR. This delays the timer tick and disrupts the RTX kernel. This is less of a
problem on Cortex-M profile-based devices, as the Cortex-M interrupt structure
supports nested interrupts. However, it is still good practice to keep the time
spent in interrupts to a minimum.
A traditional nested interrupt
scheme supports prioritized
interrupt handling, but has
unpredictable stack
requirements.
29
30
Chapter 2. Developing With an RTOS
ARM7/9-based microcontrollers do not support nested interrupts without
additional software to avoid potential deadlocks and any system based on nested
interrupts has an unpredictable stack usage. With an RTX-based application, it is
best to implement the interrupt service code as a task and assign it a high priority.
The first line of code in the interrupt task should make it wait for an event flag.
When an interrupt occurs, the ISR simply sets the event flag and terminates. This
schedules the interrupt task, which services the interrupt and then goes back to
waiting for the next event flag to be set.
Within the RTX RTOS, interrupt
code is run as tasks. The
interrupt handlers signal the tasks
when an interrupt occurs. The
task priority level defines which
task gets scheduled by the kernel.
The RTX RTOS has an event set
call, which is designed for use within an interrupt handler.
void isr_evt_set (unsigned short event_flags, OS_TID task);
A typical task intended to handle interrupts will have the following structure:
void Task3 (void)
{
while (1) {
os_evt_wait_or (0x0001, 0xffff);
…
}
// Wait until ISR triggers an event
// Handle the interrupt
// Loop and go back to sleep
}
The actual interrupt source will contain a minimal amount of code.
void IRQ_Handler (void) __irq
isr_evt_set (0x0001, tsk3);
EXTINT = 0x00000002;
VICVectAddr = 0x00000000;
{
// Signal Task 3 with an event
// Clear the peripheral interrupt flag
// Signal end of interrupt to the VIC
}
Exercise: Interrupt Events
This exercise demonstrates how to integrate interrupt handling into an RTXbased application by using event flags.
Getting Started: Building Applications with RL-ARM
Task Priority Scheme
When writing an RTOS-based application you must have a clear idea of how you
will prioritize tasks. The FIQ interrupt is the highest priority interrupt on ARM
CPUs (a non-maskable interrupt is available in Cortex processors). The FIQ is
not handled by RTX and so there is no overhead in serving it.
The remaining interrupts are handled as IRQ interrupts, which can be used to
trigger tasks (as discussed above). After the IRQ interrupts, important
background tasks may be assigned an appropriate priority level. Finally, the
round robin tasks can be assigned priority level one with the idle task running at
priority zero.
A typical RTOS priority scheme
places the FIQ and IRQ triggered
tasks at highest priority, followed
by high priority background
tasks, with round robin tasks at
lowest user task priority. The
idle task is at priority zero and
will use up any spare cycles.
Any task that is above the round
robin priority level must be a
self-blocking task, i.e. do a job
and halt. If any high priority task does not block, then it will run forever, halting
any lower priority tasks.
31
32
Chapter 2. Developing With an RTOS
Semaphores
Like events, semaphores are a method of synchronizing activity between two or
more tasks. Put simply, a semaphore is a container that holds a number of
tokens. As a task executes, it will reach an RTOS call to acquire a semaphore
token. If the semaphore contains one or more tokens, the task will continue
executing and the number of tokens in the semaphore will be decremented by
one. If there are currently no tokens in the semaphore, the task will be placed in
a WAITING state until a token becomes available. At any point in its execution,
a task may add a token to the semaphore causing its token count to increment by
one.
Semaphores are used to control
access to program resources.
Before a task can access a
resource, it must acquire a token.
If none is available, it waits.
When it is finished with the
resource, it must return the
token.
The diagram illustrates the use of
a semaphore to synchronize two
tasks. First, the semaphore must
be created and initialized with an
initial token count. In this case,
the semaphore is initialized with a single token. Both tasks run and reach a point
in their code where they will attempt to acquire a token from the semaphore. The
first task to reach this point will acquire the token from the semaphore and
continue execution. The second task will also attempt to acquire a token, but as
the semaphore is empty, it will halt execution and be placed into a WAITING
state until a semaphore token is available.
Meanwhile, the executing task can release a token back to the semaphore. When
this happens, the waiting task will acquire the token and leave the WAITING
state for the READY state. Once in the READY state, the scheduler will place
the task into the RUN state so that task execution can continue. Although
semaphores have a simple set of RTX API calls, they can be one of the more
difficult RTX objects to fully understand. In this section, we will first look at
how to add semaphores to an RTOS program and then go on to look at the most
useful semaphore applications.
Getting Started: Building Applications with RL-ARM
To use a semaphore in RTX you must first declare a semaphore container:
OS_SEM ;
Then within a task, the semaphore container can be initialized with a number of
tokens.
void os_sem_init (OS_ID semaphore, unsigned short token_count);
It is important to understand that semaphore tokens may also be created and
destroyed as tasks run. So for example, you can initialize a semaphore with zero
tokens and then use one task to create tokens into the semaphore while another
task removes them. This allows you to design tasks as producer and consumer
tasks.
Once the semaphore is initialized, tokens may be acquired and sent to the
semaphore in a similar fashion as event flags. The os_sem_wait() call is used to
block a task until a semaphore token is available, like the os_evnt_wait_or() call.
A timeout period may also be specified with 0xFFFF being an infinite wait.
OS_RESULT os_sem_wait (OS_ID semaphore, unsigned short timeout)
When a token is available in the semaphore a waiting task will acquire the token,
decrementing the semaphore token count by one. Once the token has been
acquired, the waiting task will move to the READY state and then into the RUN
state when the scheduler allocates it run time on the CPU.
When the task has finished using the semaphore resource, it can send a token to
the semaphore container.
OS_RESULT os_sem_send (OS_ID semaphore)
Like events, interrupt service routines can send semaphore tokens to a semaphore
container. This allows interrupt routines to control the execution of tasks
dependant on semaphore access.
void isr_sem_send (OS_ID semaphore)
Exercise: Semaphores
This first semaphore exercise demonstrates the basic configuration and use of a
semaphore.
33
34
Chapter 2. Developing With an RTOS
Using Semaphores
Although semaphores have a simple set of OS calls, they have a wide range of
synchronizing applications. This makes them perhaps the most challenging
RTOS objects to understand. In this section, we will look at the most common
uses of semaphores. Some are taken from “The Little Book Of Semaphores” by
Allen B. Downy, and may be freely downloaded from the URL given in the
bibliography at the end of this book.
Signaling
Synchronizing the execution of two tasks is the simplest use of a semaphore:
os_sem semB;
__task void task1 (void) {
os_sem_init (semB, 0);
while (1) {
os_sem_send (semB);
FuncA();
}
}
__task void task2 (void)
{
while (1) {
os_sem_wait (semB, 0xFFFF);
FuncB();
}
}
In this case, the semaphore is used to ensure that the code in FuncA() is executed
before the code in FuncB().
Multiplex
A multiplex semaphore limits the number of tasks that can access a critical
section of code. For example, routines that access memory resources and can
support a limited number of calls.
os_sem Multiplex;
void task1 (void) __task
{
os_sem_init (Multiplex, 5);
while (1) {
os_sem_wait (Multiplex, 0xFFFF);
ProcessBuffer ();
os_sem_send (Multiplex);
}
}
Getting Started: Building Applications with RL-ARM
35
Here, the multiplex semaphore has five tokens. Before a task can continue, it
must acquire a token. Once the function finished, the token is sent back. If more
than five tasks are calling ProcessBuffer(), the sixth task must wait until a
running task finishes and returns its token. Thus, the multiplex ensures that a
maximum of 5 instances of the ProcessBuffer() function may be called at any one
time.
Exercise: Multiplex
This exercise demonstrates the use of a multiplex to limit the number of
illuminated LEDs.
Rendezvous
A more generalized form of semaphore signaling is a rendezvous. A rendezvous
ensures that two tasks reach a certain point of execution. Neither may continue
until both have reached the rendezvous point.
os_sem Arrived1, Arrived2;
__task void task1 (void)
{
__task void task2 (void)
os_sem_init (Arrived1, 0);
os_sem_init (Arrived2, 0);
while (1) {
FuncA1 ();
os_sem_send (Arrived1);
os_sem_wait (Arrived2, 0xFFFF);
FuncA2 ();
}
}
{
while (1) {
FuncB1 ();
os_sem_send (Arrived2);
os_sem_wait (Arrived1, 0xFFFF);
FuncB2 ();
}
}
In the example above, the two semaphores ensure that both tasks will rendezvous
and proceed then to execute FuncA2() and FuncB2().
Exercise: Rendezvous
This exercise uses rendezvous semaphores to synchronize the activity of two
tasks.
36
Chapter 2. Developing With an RTOS
Barrier Turnstile
Although a rendezvous is very
useful for synchronizing the
execution of code, it only works
for two functions. A barrier is a
more generalized form of
rendezvous, which works to
synchronize multiple tasks. A
barrier is shared between a
defined number of tasks. As
each task reaches the barrier it
will halt and de schedule. When
all of the tasks have arrive at the
barrier it will open and all of the tasks will resume execution simultaneously.
The barrier uses semaphores to build a code object called a turnstile. The
turnstile is like a gate. Initially the turnstile gate is locked. When all of the tasks
have arrived at the turnstile, it will open allowing all of the tasks to continue
‘simultaneously’. Once the critical code has been executed each task will pass
through a second exit turnstile. The exit turnstile is used to lock the first entry
turnstile and reset the barrier object so the barrier can be reused.
The barrier object is a sophisticated use of semaphores so it its worth spending
some time studying it. The barrier object uses three semaphores, the entry
turnstile, Entry_Turnstile, the exit turnstile, Exit_Turnstile, and a Mutex, which
ensures that only one task at a time executes the critical code section. The
general structure of the barrier is:
while(1) {
Entry Turnstile code
Synchronised code section
Exit Turnstile code
}
Getting Started: Building Applications with RL-ARM
37
The code for the entry turnstile is duplicated in each of the participating tasks:
os_sem_init(Mutex, 1);
os_sem_init(Entry_Turnstile, 0);
os_sem_init(Exit_Turnstile, 1);
count = 0;
……………
while (1) {
……………
os_sem_wait (Mutex, 0xffff);
count = count+1;
if (count==4) {
os_sem_wait (Exit_Turnstile, 0xffff);
os_sem_send (Entry_Turnstile);
}
os_sem_send (Mutex);
os_sem_wait (Entry_Turnstile, 0xffff);
os_sem_send (Entry_Turnstile);
// Begin critical section
// End critical section
// Turnstile gate
In this example, a barrier synchronizes four tasks. As the first task arrives, it will
increment the count variable. Execution continues until it reaches the turnstile
gate os_sem_wait(Entry_Turnstile,0xffff). At this point, the Entry_Turnstile
semaphore is zero. This will cause the task to halt and de-schedule. The same
will happen to the second and third task. When the fourth task enters the
turnstile, the value of count will become four. This causes the
if( count == 4) statement to be executed. Now, a token is placed into the
Entry_Turnstile semaphore. When the fourth task reaches the
os_sem_wait(Entry_Turnstile,0xffff) statement, a token is available, so it can
continue execution. The turnstile gate is now open. Once the fourth task has
passed through the gate, it places a token back into the Entry_Turnstile
semaphore. This allows a waiting task to resume execution. As each waiting
task resumes, it writes a token into the Entry_Turnstile semaphore. The Mutex
semaphore locks access to the critical section of the turnstile. The Mutex
semaphore ensures that each task will exclusively execute the critical section. In
the critical section, the last arriving task will also remove a token from
Exit_Turnstile. This closes the gate of the Exit_Turnstile, as we shall see below.
os_sem_wait (Mutex, 0xffff);
count = count-1;
if (count==0) {
os_sem_wait (Entry_Turnstile,0xffff);
os_sem_send (Exit_Turnstile);
}
os_sem_send (Mutex);
os_sem_wait (Exit_Turnstile,0xffff); );
os_sem_send (Exit_Turnstile);
}
// Begin critical section
// End critical section
// Turnstile gate
38
Chapter 2. Developing With an RTOS
Semaphore Caveats
Semaphores are an extremely useful feature of any RTOS. However,
semaphores can be misused. You must always remember that the number of
tokens in a semaphore is not fixed. During the runtime of a program, semaphore
tokens may be created and destroyed. Sometimes this is useful, but if your code
depends on having a fixed number of tokens available to a semaphore, you must
be very careful to return tokens always back to it. You should also rule out the
possibility of creating additional new tokens.
Mutex
Mutex stands for “Mutual Exclusion”. A Mutex is a specialized version of a
semaphore. Like a semaphore, a Mutex is a container for tokens. The difference
is that a Mutex is initialized with one token. Additional Mutex tokens cannot be
created by tasks. The main use of a Mutex is to control access to a chip resource
such as a peripheral. For this reason, a Mutex token is binary and bounded.
Apart from this, it really works in the same way as a semaphore. First, we must
declare the Mutex container and initialize the Mutex:
os_mut_init (OS_ID mutex);
Then any task needing to access the peripheral must first acquire the Mutex
token:
os_mut_wait (OS_ID mutex, U16 timeout);
Finally, when we are finished with the peripheral, the Mutex must be released:
os_mut_release (OS_ID mutex);
Mutex use is much more rigid than semaphore use, but is a much safer
mechanism when controlling absolute access to underlying chip registers.
Exercise: Mutex
This exercise uses a Mutex to control access to the microcontroller UART.
Getting Started: Building Applications with RL-ARM
Mutex Caveats
Clearly, you must take care to return the Mutex token when you are finished with
the chip resource, or you will have effectively prevented any other task from
accessing it. You must also be careful about using the os_task_delete() call on
functions that control a Mutex token. RTX is designed to be a small footprint
RTOS. Consequently, there is no task deletion safety. This means that if you
delete a task that is controlling a Mutex token, you will destroy the Mutex token
and prevent any further access to the guarded peripheral.
Mailbox
So far, all of the inter-task communication methods have only been used to
trigger execution of tasks: they do not support the exchange of program data
between tasks. Clearly, in a real program we will need to move data between
tasks. This could be done by reading and writing to globally declared variables.
In anything but a very simple program, trying to guarantee data integrity would
be extremely difficult and prone to unforeseen errors. The exchange of data
between tasks needs a more formal asynchronous method of communication.
RTX contains a mailbox system that buffers messages into mail slots and
provides a FIFO queue between the sending and receiving tasks. The mailbox
object supports transfer of single variable data such as byte, integer and wordwidth data, formatted fixed length messages, and variable length messages. We
will start by having a look at configuring and using fixed length messaging. For
this example, we are going to transfer a message consisting of a four-byte array
that contains nominally ADC results data and a single integer of I/O port data.
unsigned char ADresult [4];
unsigned int PORT0;
To transfer this data between tasks, we need to declare a suitable data mailbox.
A mailbox consists of a buffer formatted into a series of mail slots and an array
of pointers to each mail slot.
A mailbox object consists of a memory block formatted into message buffers and
a set of pointers to each buffer.
39
40
Chapter 2. Developing With an RTOS
To configure a mailbox object
we must first declare the
message pointers. Here we are
using 16 mail slots. This is an
arbitrary number and varies
depending on your requirements,
but 16 is a typical starting point.
The message pointers are
declared as an array of unsigned
integers using the following
macro:
os_mbx_declare (MsgBox, 16);
Next, we must declare a structure to hold the data to be transferred. This is the
format of each message slot:
typedef struct {
unsigned char ADresult [4];
unsigned int PORT0;
} MESSAGE;
Once we have defined the format of the message slot, we must reserve a block of
memory large enough to accommodate 16 message slots:
_declare_box (mpool, sizeof (MESSAGE), 16);
This block of memory then has to be formatted into the required 16 mail slots
using a function provided with the RTOS:
_init_box (mpool, sizeof (mpool), sizeof (MESSAGE));
Now, if we want to send a message between tasks, we can create a pointer of the
message structure type and allocate it to a mail slot.
MESSAGE *mptr;
mptr = _allocbox (mpool);
Next, we fill this mail slot with the data to be transferred:
for (int i=0; i<4; i++) {
mptr->ADresult [i] = ADresult (i);
mptr->PORT0 = IOPIN0;
}
Getting Started: Building Applications with RL-ARM
Then we send the message.
os_mbx_send (MsgBox, mptr, 0xffff);
In practice, this locks the mail slot protecting the data, and the message pointer is
transferred to the waiting task. Further messages can be sent using the same
calls, which will cause the next mail slot to be used. The messages will form a
FIFO queue. In the receiving task, we must declare a receiving pointer with the
message structure type. Then we wait for a message with the os_mxb_wait() call.
This call allows us to nominate the mailbox that we want to use, provide the
pointer to the mail slot buffer, and specify a timeout value.
MESSAGE *rptr;
When the message is received, we can simply access the data in the mail slot and
transfer them to variables within the receiving task.
pwm_value = *rptr->ADresult [0];
Finally, when we have made use of the data within the mail slot it can be released
so that it can be reused to transfer further messages.
_free_box (mpool, rptr);
The following code shows how to put all this together. First the initializing code
that may be called before RTX is started.
typedef struct {
unsigned char ADresult [4];
unsigned int PORT0;
} MESSAGE;
unsigned int mpool [16 * sizeof (MESSAGE) / 4 + 3];
_declare_box (mpool, sizeof (MESSAGE), 16);
main() {
…
_init_box (mpool, sizeof (mpool), sizeof (MESSAGE));
os_sys_init (Send_Task);
…
}
41
42
Chapter 2. Developing With an RTOS
A task sending a message:
__task void Send_Task (void) {
…
MESSAGE *mptr;
os_mbx_init (MsgBox, sizeof (MsgBox));
tsk1 = os_tsk_self ();
tsk2 = os_tsk_create (Receive_Task, 0x1);
while (1) {
mptr = _alloc_box (mpool);
for (i=0; i < 4 ; i++) {
Mptr->ADresult [i] = ADresult (i);
Mptr->PORT0 = IOPIN0;
}
os_mbx_send (MsgBox, mptr, 0xffff);
…
}
// Acquire a mailbox
// Fill it with data
// Send the message
}
A task to receive the message:
__task void Receive_Task (void) {
…
MESSAGE *rptr;
while (1) {
os_mbx_wait (MsgBox, &rptr, 0xffff);
pwm_value = *rptr->ADresult [0];
_free_box (mpool, rptr);
…
}
}
//
//
//
//
Wait for a message arrives
Read the message data
Free the mail slot
Use the data in this task
Exercise: Mailbox
This exercise presents the minimum code to initialize a mailbox and then pass a
formatted message between two tasks.
Getting Started: Building Applications with RL-ARM
Task Lock and Unlock
In a real application, it is often necessary to ensure that a section of code runs as
a contiguous block, so that no interrupts occur while it is executing. In an RTXbased application, this cannot be guaranteed, as the scheduler is continually
interrupting each task. To ensure a continuous execution, you must use the task
lock and task unlock system calls, which disable and re-enable the scheduler:
tsk_lock ();
do_critical_section ();
tsk_unlock ();
The critical section of code must be kept to a minimum, as a long period with the
scheduler disabled will disrupt the operation of the RTOS. The source code for
the tsk_lock() and tsk_unlock() functions on the OS_LOCK and OS_UNLOCK
macros are located in the RTX_Config.c file and may be modified to meet any
special requirements.
Configuration
So far, we have looked at the RTX API. This includes task management
functions, time management, and inter-task communication. Now that we have a
clear idea of exactly what the RTX kernel is capable of, we can take a more
detailed look at the configuration file. As mentioned at the beginning, you must
select the correct RTX_Config.c for the microcontroller that you are using. All
supported microcontrollers have a pre-configured configuration file, so RTX only
needs minimal configuration.
Like the other configuration files, the RTX_Config.c file is a template file that
presents all the necessary configurations as a set of menu options.
43
44
Chapter 2. Developing With an RTOS
Task Definitions
In the Task Definitions section, we define the basic resources that will be
required by the tasks. For each task, we allocate a default stack space (in the
above example this is 200 bytes). We also define the maximum number of
concurrently running tasks. Thus, the amount of RAM required for the above
example can be calculated easily as 200 x 6 = 1,200 bytes. If some of our tasks
need a larger stack space, they must be started with the os_task create_usr() API
call. If we are defining custom stack sizes, we must define the number of tasks
with custom stacks. Again, the RAM requirement can be calculated easily.
During development, RTX can be set up to trap stack overflows. When this
option is enabled, an overflow of a task stack space will cause the RTX kernel to
call the os_stk_overflow() function that is located in the RTX_Config.c file. This
function gets the TASK ID of the running task and then sits in an infinite loop.
The stack checking option is intended for use during debugging and should be
disabled on the final application to minimize the kernel overhead. However, it is
possible to modify the os_stack_overflow() function, if enhanced error protection
is required in the final release.
The final option in the Task Definitions section allows you to define the number
of user timers. It is a common mistake to leave this set at zero. If you do not set
this value to match the number of virtual timers in use by your application, the
os_timer() API calls will fail to work.
For Cortex-based microcontrollers the Task Definitions section has one
additional option. Disabling the “run in privileged mode” tick box allows the
RTOS kernel to run in Handler Mode with privileged access, while the user tasks
run in Thread Mode with unprivileged access. This means that the RTX kernel
has full access to the microcontroller resources and its own stack space while the
application code has limited access to the microcontroller resources. For
example, it cannot access the Cortex interrupt control registers. This can be very
useful for safety critical code where we may need to partition the user task code
from the kernel code.
Getting Started: Building Applications with RL-ARM
System Timer Configuration
The system timer configuration section defines which on-chip timer will be used
to generate a periodic interrupt to provide a time base for the scheduler. On
ARM7 and ARM9-based microcontroller, you need to make use of a generalpurpose timer available in the silicon. With a Cortex-based microcontroller,
there is no need to select a timer, as the Cortex processor contains a dedicated
SysTick timer, which is intended to be used by an RTOS. In both cases, we must
next define the input frequency to the timer. For an ARM7 or ARM9-based
microcontroller this will generally be the advanced peripheral bus frequency. For
a Cortex-MX-based microcontroller it will generally be the CPU frequency.
Next, we must define our timer tick rate. Timer interrupts are generated at this
rate. On each timer tick, the RTOS kernel will check for RTOS features
(scheduler, events, semaphores, etc) and then schedule the appropriate action.
Thus, a high tick rate makes the RTOS more sensitive to events, at the expense of
continually interrupting the executing task. The timer tick value will depend on
your application, but the default starting value is set to 10ms.
Round Robin Task Switching
The final configuration setting allows you to enable round robin scheduling and
define the time slice period. This is a multiple of the timer tick rate, so in the
above example, each task will run for five ticks or 50ms before it will pass
execution to another task of the same priority that is ready to run. If no task of
the same priority is ready to run, it will continue execution.
Scheduling Options
RTX allows you to build an application with three different kernel-scheduling
options. These are:
Pre-emptive scheduling,
Round robin scheduling, and
Co-operative multi-tasking.
45
46
Chapter 2. Developing With an RTOS
Pre-emptive Scheduling
If the round robin option is disabled in the RTX_Config.c file, each task must be
declared with a different priority. When the RTOS is started and the tasks are
created, the task with the highest priority will run.
In a pre-emptive RTOS, each
task has a different priority level
and will run until it is pre-empted
or has reached a blocking OS
call.
This task will run until it blocks,
i.e. it is forced to wait for an
event flag, semaphore, or other object. When it blocks, the next ready task with
the highest priority will be scheduled and will run until it blocks, or a higher
priority task becomes ready to run. Therefore, with pre-emptive scheduling we
build a hierarchy of task execution, with each task consuming variable amounts
of run time.
Round Robin Scheduling
A round-robin-based scheduling scheme can be created by enabling the round
robin option in the RTX_Config.c file and declaring each task with the same
priority.
In a round robin RTOS tasks will
run for a fixed period, or time
slice, or until they reach a
blocking OS call.
In this scheme, each task will be
allotted a fixed amount of run time before execution is passed to the next ready
task. If a task blocks before its time slice has expired, execution will be passed to
the next ready task.
Getting Started: Building Applications with RL-ARM
Round Robin Pre-emptive Scheduling
As discussed at the beginning of this chapter, the default scheduling option for
RTX is round robin pre-emptive. For most applications, this is the most useful
option and you should use this scheduling scheme unless there is a strong reason
to do otherwise.
Co-operative Multitasking
A final scheduling option is co-operative multitasking. In this scheme, round
robin scheduling is disabled and each task has the same priority. This means that
the first task to run will run forever unless it blocks. Then execution will pass to
the next ready task.
In a co-operative RTOS, each
task will run until it reaches a
blocking OS call or uses the
os_tsk_pass() call.
Tasks can block on any of the
standard OS objects, but there is
also an additional system call, os_task_pass(), that schedules a task to the
READY state and passes execution to the next ready task.
Priority Inversion
Finally, no discussion of RTOS scheduling would be complete without
mentioning priority inversion.
A priority inversion is a common
RTOS design error. Here, a high
priority task may become
delayed or permanently blocked
by a medium priority task.
In a pre-emptive scheduling
system, it is possible for a high
priority task T1 to block while it
calls a low priority task T3 to perform a critical function before T1 continues.
47
48
Chapter 2. Developing With an RTOS
However, the low priority task T3 could be pre-empted by a medium priority task
T2. Now, T2 is free to run until it blocks (assuming it does) before allowing T3
to resume completing its operation and allowing T1 to resume execution. The
upshot is the high priority task T1 that is blocked and that becomes dependent on
T2 to complete before it can resume execution.
os_tsk_prio (tsk3, 10);
os_evt_set (0x0001, tsk3);
os_evt_wait_or (0x0001, 0xffff);
os_tsk_prio (tsk3, 1);
//
//
//
//
raise the priority of task3
trigger it to run
wait for Task3 to complete
lower its priority
The answer to this problem is priority elevation. Before T1 calls T3 it must raise
the priority of T3 to its level. Once T3 has completed, its priority can be lowered
back to its initial state.
Exercise: Priority Inversion
This exercise demonstrates a priority inversion and priority elevation.
Getting Started: Building Applications with RL-ARM
Chapter 3. RL-Flash Introduction
This chapter discusses configuring and using the RL-Flash embedded file system.
To many experienced developers of small embedded systems, the concept of
using a file system may be considered something of a luxury. However,
technology has moved on, and, as we saw in the first chapter, ARM processorbased microcontrollers now have the processing power to make using an RTOS
practical. They also have the memory resources to support the use of embedded
file systems. Adding a file system to a small-embedded system allows you to
build applications that are far more complex.
The file system can be used to store program data during deep power saving
modes, or for holding program constants, or even for storing firmware upgrades
for a bootloader. In short, a file system is a new and extremely useful tool for
developers of small, embedded systems.
The RL-Flash file system allows you to place a file system in most common
memory types including SRAM, Parallel Flash, Serial Flash, and SD/MMC
cards. In the case of SD/MMC cards, FAT12, FAT16, and FAT32 are supported.
As we will see in later chapters, the file system can be accessed through the USB
Mass Storage Class and through Ethernet with the Trivial File Transfer Protocol
(TFTP). This provides an easy and well-understood method of accessing your
data. Throughout this chapter, we will first discuss configuring a RAM-based
file system that occupies the internal SRAM of a small microcontroller. Then we
will use this file system to review the ANSI file I/O functions available within
RL-Flash. In the remainder of the chapter, we will discuss configuring the file
system for the remaining memory formats.
Getting Started
In this first section, we will look at configuring the file system to use the internal
RAM of a typical ARM processor-based microcontroller. Although this is not
usually practical in real embedded systems, as all data would be lost once power
is removed from the microcontroller, it does give us an easy starting point with
which to practice our file handling skills.
49
50
Chapter 3. RL-Flash Introduction
Setting-Up the File System
The RL-Flash file system can be used standalone or in conjunction with RTX.
The file system library functions are re-entrant and thread safe. Therefore, with
RTX, any task can access the file system. Note that, if the code is build with the
MDK-ARM, MicroLIB is not supporting the stdlib functions used by the file
system, and so you must use the default ARM Compiler libraries.
The RL-Flash file system can use on-chip or external SRAM. If an external
SRAM is used, provide the initializing code to configure the external bus of the
microcontroller. In the examples below, we configure the file system to use the
internal RAM of a typical microcontroller.
The minimal configuration of the RL-FlashFS file
system consists of its library and a configuration file.
Set the project heap size to a minimum of 0x1000.
Our first file system project consists of the startup code
and the Retarget.c library support file. Also, add the file
system library FS_ARM_L.lib for ARM7/9-based devices
and FS_CM3.lib for Cortex-M-based devices. The files are
located in the library directory,
such as C:\KEIL\ARM\RV31\LIB for
the 3.1 compiler version, where
as File_Config.c is located in
C:\KEIL\ARM\RL\FLASHFS\SRC.
Once these files are part of the
project, create a module, main.c
that contains the source code.
All the necessary configuration
is done in the startup code and
the File_Config.c file.
Getting Started: Building Applications with RL-ARM
The file system buffers data in dynamically allocated memory, so we must
reserve heap space in the startup code.
In File_Config.c, enable the drive type we
want to use and set its parameters. If
several drives are used, a default drive
can also be defined. It is possible to
enable the file system volumes and place
them on different physical media,
including internal/external parallel Flash
memory, SPI EEPROM, internal or
external parallel SRAM, and MultiMedia/SD memory cards. When configured,
each drive has a default drive letter as shown in the table.
We will discuss each of these formats in turn, but for now we will define a file
system in on-chip RAM. This is quick and simple to configure and can be
debugged in both real target hardware via a ULINK® USB-JTAG Adapter and in
simulation. Once the project has been defined and all of the modules have been
added, we simply need to configure the base address of the file system in RAM,
its size in memory and the number of sectors. The file system may be located in
any valid region of RAM and has a minimum size of 16K. The number of
sectors that you have depends on how you intend to use the file system. If you
intend to have a small number of large files, then select a small sector number.
If, on the other hand, you expect to create a large number of small files, then
select a large number of sectors. You can select between 8, 16, 32, 64, and 128
sectors per volume drive. Once configured, we can add the necessary code to
initialize the volume for use within our application code.
if (fcheck ("R:") != 0) {
if (fformat ("R:") != 0)
…
}
}
{
// check for a formatted drive
// format the drive
// error handling code
The fcheck() function can be used to determine if there is a valid formatted
volume present. The fformat() function can be used at any time to
format/reformat the drive. After formatting all the drive memory contents will be
set to 0x00.
Exercise: First File System
This first file system project guides you through setting up a RAM-based file
system. This can run on real hardware or within the µVision Simulator.
51
52
Chapter 3. RL-Flash Introduction
File I/O Routines
Once the file system has been configured, we can manipulate files.
Function
Description
fopen
Creates a new file or opens an existing file.
fclose
Writes buffered data to a file and then closes the file.
fflush
Writes buffered data to a file.
To create a file, open a file on the volume and define a handler to it. This
handler, with which we can read and write to the file, is a pointer to the open file.
#include
FILE *Fptr;
Include the stdio.h library to define our file handler as type FILE. Next, create a
file and check that it has opened. fopen() requires a string for the file name and
an access attribute, which can be “w” write, “a” append, or “r” read.
Fptr = fopen ("Test.txt","w");
If the file cannot be created or opened, a NULL pointer is returned.
if (Fptr == NULL)
…;
}
{
// error handler
Once you have finished using the file, you must close it by calling fclose(). Up to
this point, all data written to the file is buffered in the heap space. When you
close the file, the data is written to the physical storage media. Consider this
carefully if you have multiple file streams or are storing large streams of data.
fclose (Fptr);
Once we have created a file, a number of functions help us work with it.
Function
Description
feof
Reports whether the end of the file stream has been reached.
ferror
Reports whether there is an error in the file steam.
fseek
Moves the file stream in pointer to a new location.
ftell
Gets the current location of the file pointer.
rewind
Moves the file stream in file pointer to the beginning of the file.
Getting Started: Building Applications with RL-ARM
feof() returns zero until the end of file is reached. Notice, it is possible to read
past the end of a file. While reading or writing data, ferror() reports access errors
in the file stream. Once an error has occurred, ferror() returns the error code
until the file is closed, or until the file pointer is rewound to the start of the file.
fseek(), ftell(), and rewind() position the file pointer within the file. fseek()
moves the file pointer to a location within a file. This location is defined relative
to an origin, which can be the start or the end of the file, or the current file
pointer position. ftell() reports the current location of the file pointer relative to
the beginning of the file. rewind() places the file pointer at the start of the file.
rewind (Fptr);
// Place file-pointer at the start of file
fseek (Fptr, 4, SEEK_CUR); // Move 4 chars forward rel. to the FP location
location = ftell (Fptr);
// Read the file pointer location
Four standard functions exist to write data to a file stream in byte, character,
string, or formatted output format. Similarly, four analogous functions exist to
read data.
Function
Description
fwrite
Writes a number of bytes to the file stream.
fputc
Writes a character to the file stream.
fputs
Writes a string to the file stream.
fprintsf
Writes a formatted string to the file stream.
fread
Reads a number of bytes from the file stream.
fgetc
Reads a character from the file stream.
fgets
Reads a string from the file stream.
fscanf
Reads a formatted string from the file stream.
while (!feof (Fptr)) {
byte = fgetc (Fptr);
if (ferror (Fptr)) {
…;
}
}
// Error handling
Exercise: File Handling
This project contains several examples, which demonstrate creating files,
reading and writing data, and managing the data within a file.
53
54
Chapter 3. RL-Flash Introduction
Volume Maintenance Routines
As you create and update files, it is important to maintain the health of the drive.
A number of functions maintain the volume and manipulate the content of the
drive. The file system provides five drive and three file maintenance functions.
Function
Description
fformat
Formats the drive.
fcheck
Checks the consistency of the drive.
ffree
Reports the free space available in the drive.
fanalyse
Checks the drive for fragmentation.
fdefrag
Defragments the drive.
We have already used the fcheck() and fformat() functions. The additional drive
maintenance functions include ffree() that will report the available free disk space
and fanalyse() that can be used to check the fragmentation level of a selected
drive. This function returns a value 0 – 255 to indicate the current level of drive
fragmentation. Once a drive becomes too fragmented, the fdefrag() function may
be used to reorganize the volume memory and maximize the available space.
if (ffree ("R:") < THRESHOLD)
if (fanalyse ("R:") > 100)
fdefrag ("R:");
}
}
{
{
// When free space reaches a minimum
// Check the fragmentation
// If necessary defrag the drive
Function
Description
fdelete
Deletes a selected file.
frename
Renames a selected file.
ffind
Locates files by name or extension.
Three functions are also provided to allow you to manage the files stored within
the drive volume. The functions frename() and fdelete() allow you to rename and
delete a selected file within a chosen drive.
frename ("R:Test1.txt", "New_Test.txt");
fdelete ("R:Test2.txt");
// Rename file
// Delete file
You can also search the contents of the drive with the ffind() function. This will
find files that match a specified pattern. When a file is found, its details are
reported in a structure called info.
Getting Started: Building Applications with RL-ARM
//Create a file to store the directory listing
FINFO info;
Fptr = fopen ("directory.log", "w");
while (ffind ("R:*.*", &info) == 0) {
// Search drive for all files
fprintf (Fptr, "\nname %s %5d bytes ID: %04d",
info.name, info.size, info.fileID);
}
fclose (Fptr);
In addition to containing records for file details, the FINFO structure also
contains fields to hold a timestamp of the creation time or modification time of
the file.
typedef struct {
S8 name [256];
U32 size;
U16 fileID;
U8 attrib;
struct {
U8 hr;
U8 min;
U8 sec;
U8 day;
U8 mon;
U16 year;
} time;
} FINFO;
//
//
//
//
//
Search info record
Name
File size in bytes
System Identification
Attributes
//
//
//
//
//
//
//
Hours
[0..23]
Minutes [0..59]
Seconds [0..59]
Day
[1..31]
Month
[1..12]
Year
[1980..2107]
Create/Modify Time
The time and calendar information is provided through two functions held in the
file fs_time.c.
U32 fs_get_time (void);
U32 fs_get_date (void);
If you want to use time and date information within your application, you must
modify these two functions to access the real-time clock on your microcontroller.
You must also add the code to initialize the real-time clock. The file fs_time.c is
located in C:\KEIL\ARM\RL\FLASHFS\SRC. You can rebuild the library with the
project in C:\KEIL\ARM\RL\FLASH to provide a custom library for your application.
When rebuilding the library, be careful to select either the ARM_LE (ARM7/9
Little-Endian) or Cortex as the target. The fs_time.c functions are not supported if
you are using a RAM-based file system.
Exercise: Drive Functions
This project contains several examples, which demonstrate maintaining and
working with a drive volume.
55
56
Chapter 3. RL-Flash Introduction
Flash Drive Configuration
Although a RAM-based file system can be battery-backed, or can be used to store
temporary files during the run time of an application, we usually think of a file
system as having non-volatile storage. To this end, we can configure the file
system to use the internal Flash memory of a microcontroller, or external parallel
Flash, which is memory-mapped onto the microcontroller’s external bus.
First, modify the File_Config.c file.
This time select the Flash drive
as the target drive. Once
selected, we must define the
start. Next, configure the target
base address and drive size. This
should map onto the Flash
sectors of the microcontroller’s
memory. Next, we must add the
programming algorithms for the internal Flash memory. These algorithms are
defined for supported microcontrollers and are located in
C:\KEIL\ARM\RL\FLASHFS\FLASH.
Each subdirectory contains the necessary support files for a given microcontroller
or parallel Flash device. Each of these directories contains FS_FlashDev.h and
FS_FlashPrg.c. Copy these files to your project directory and add FS_FlashPrg.c to
your project. If there is no direct support for your particular microcontroller, do
not be concerned; we will look at developing Flash drivers next.
To use parallel Flash as a file system we
must add two new files:
1. The FS_Flash.h include file that contains a
mapping of the physical Flash sectors.
2. The Flash_page.c file that contains the low
level Flash write and erase routines.
The file FS_FlashPrg.c provides the necessary Flash programming algorithm and
FS_FlashDev.h provides the mapping to the physical Flash sectors. The FlashDev.h
file maps all of the available Flash sectors to the file system by default. We must
modify this file to map only the sectors that are actually being used by our file
system.
Getting Started: Building Applications with RL-ARM
// Flash sector definitions in Flash_Page.c
//
#define FLASH_DEVICE
\
DFB (0x008000, 0x000000), \
/* Sector size, Start address */
DFB (0x008000, 0x008000), \
/* Sector size, Start address */
#define FL_NSECT 2
// File_Config.c as displayed as in the µVision Configuration Wizard
//
Traget device Base address 0x0000 8000
Device Size in bytes
0x0001 0000
Each physical Flash sector used by the file system must be included in the
FLASH_DEVICE definition. Each sector definition includes the size of the
sector and its address as an offset from the target device base address that is set in
file config.h. In the example above we are defining a file system located at the
32KB boundary of size 64KB. In the physical Flash memory on the
microcontroller, this occupies two Flash sectors each of 32KB. Finally, we must
set the FL_NSECT define to the number of physical Flash sectors used by the file
system in this case two.
Once you have added these files to your project and made the necessary
configuration changes, the Flash file system is ready to use. The function calls
that we used for the RAM-based system work in exactly the same way for the
Flash-based system. Before using the Flash-based file system, the application
code must call finit() before performing any other file system operations.
void main (void)
finit ();
...
}
{
Exercise: Flash File System
This exercise demonstrates how to locate a file system in the internal Flash
memory of an ARM processor-based microcontroller.
57
58
Chapter 3. RL-Flash Introduction
Adapting Flash Algorithms for RL-Flash
If RL-Flash does not provide direct support for your microcontroller or the
parallel Flash on your board, it is possible to adapt the programming algorithms,
used by the Keil ULINK USB-JTAG adapter family, to use them as drivers for
the Flash file system. The ULINK family Flash programming algorithms are
located in C:\KEIL\ARM\FLASH.
For each microcontroller, the ULINK programming algorithms are included in
two files: FlashPrg.c and FlashDev.c. Copy these files to a new directory and
rename FlashPrg.c to FS_FlashPrg.c.
This file contains the basic low-level programming algorithm required by the file
system. To make the programming algorithms compatible with the file system
you must make the following changes. First, change the include file name from:
#include "..\FlashOS.H"
to #include .
Next, rename the following functions:
from int Init (unsigned long adr, unsigned long clk, unsigned long fnc)
to
int fs_Init (U32 adr, U32clk),
from int EraseSector (unsigned long adr)
to
int fs_EraseSector (U32 adr),
and
int
to
ProgramPage (unsigned long adr, unsigned long sz, unsigned char *buf)
int fs_ProgramPage (U32 adr, U32 sz, U8 *buf).
Finally, delete the functions UnInit() and EraseChip().
Depending on the underlying Flash technology, you may need to modify the
program page function. This will depend on the write granularity of the Flash
memory. Generally, you can use the program page function without
modification if the Flash memory can be written with a word at a time.
However, you will need to add the packed attribute to the data buffer to allow for
unaligned buffer access. Change
to
M16 (adr) = *((unsigned short *) buf);
M16 (adr) = *((__packed unsigned short *) buf);
If the write granularity of the Flash memory is larger than a word, i.e. the Flash
memory has a minimum write page size of 128 bytes, it will be necessary to
Getting Started: Building Applications with RL-ARM
59
provide some extra code to manage the Flash page size. Typically, this code has
to read the current data stored in the Flash page, concatenate this with the new
data stored in the file system buffer, and then write the updated page to the Flash
memory. The code below can be used as a starting point for such a device.
#define PAGE_SZ 1024
U32 Page [PAGE_SZ/4];
int fs_ProgramPage (U32 adr, U32 sz, U8 *buf)
unsigned long i;
// Page Size
// Page Buffer
{
for (i = 0; i < ((sz+1)/2); i++) {
M16 (adr & ~3) = CMD_PRGS;
// Write Program Set-up Command
M16 (adr) = *((__packed unsigned short *) buf); // Write 2 byte data
if (WaitWithStatus(adr & ~3) & (PS | SP))
// Unsuccessful
return (1);
buf += 2;
adr += 2;
}
return (0);
// Done successfully
}
In addition to the programming algorithms, you will need to define the Flash
sector definitions in FS_FlashDev.h.
The file FlashDev.c contains the sector definitions for the ULINK programming
algorithms, so you can use this as a basis for the FS_FlashDev.h file or alternatively
you can modify an existing FS_FlashDev.h file. Either way the FlashDev.h file must
conform to the format described above.
If you are using the internal microcontroller Flash memory, you should locate the
file system into a region where there are multiple small sectors, as this will
reduce the amount of erasing and buffering required. Also, since the RL-Flash
file system does not support wear leveling, you must bear in mind an estimated
number of writes to the file system over the life time of the final product.
Typically, microcontroller Flash memory is rated at 100K write cycles. If you
are likely to exceed this, you should consider using an SD/MMC card since these
formats support wear leveling in hardware.
60
Chapter 3. RL-Flash Introduction
MultiMedia Cards
The easiest way to add a large amount of low cost data storage to a small
microcontroller system is through a Secure Digital (SD) or Multi Media Card
(MMC). These cards are available in ever increasing densities at ever lower
prices. Although they are available from a wide range of manufacturers, the
cards conform to a standard specification that defines the interface protocol
between the microcontroller and the memory card. The SD and MMC protocols
allow the microcontroller to communicate in a serial SPI mode at 25KBytes/sec
or through a 4-bit-wide bus at 100KBytes/sec. In order to use the memory card
in parallel mode, the microcontroller must have a dedicated Multimedia Card
Interface (MCI) peripheral. If this is the case, a dedicated driver for supported
microcontrollers is provided in C:\KEIL\ARM\RL\FLASHFS\DRIVERS.
Simply select the appropriate MCI driver and add this to your project as shown
below:
To configure RL-Flash to use an SD\MM card add the MCI or SPI driver for
your microcontroller and configure File_Config.c to use the memory card. In
File_Config.c we must enable the memory card and select it as the default drive. It
is possible to configure the memory card based file system to use an additional
cache of RAM within the microcontroller. This can be from 1K up to 32KBytes
in size. It is really only necessary to enable this option if you are using a
dedicated MCI peripheral. With the cache enabled, the MCI peripheral is able to
perform multiple sector writes and multiple sector reads. If you are using an SPI
peripheral to communicate with the SD/MMC card, you will not get any
significant performance gains with the cache enabled. The RL-Flash makes use
of the Direct Memory Access (DMA) peripherals within supported
microcontrollers to stream data to and from the SD/MMC card. If the DMA is
limited to certain regions of memory, the “relocate buffer cache” option allows
you to force the file buffer cache into a suitable region. Do check that this is
correct for your particular microcontroller.
Getting Started: Building Applications with RL-ARM
From this point onwards, the file system API can be used as normal. However,
as we are communicating with an external memory card, which may have some
timing latencies, it may fail the finit() call. To ensure that the file system always
initializes correctly, it is advisable to allow for retries as shown below.
count = 3;
while (finit() != 0)
if (!(count--)) {
errorflag = 1;
break;
}
}
{
By default, the file system uses the FAT16 file format. It is possible to enable
FAT32 support for SD/MMC-based file systems. A memory card can be
formatted with a FAT32 file system as follows:
fformat ("M:SD_CARD / FAT32");
A full erase of the card can also be performed during a format as follows:
fformat ("M:SD_CARD / WIPE");
If your microcontroller does not have a dedicated MCI peripheral, then it is
possible to configure the file system library to communicate with the memory
card in SPI mode. SPI driver files are provided in the same file system drivers’
directory. You simply need to add the SPI driver in place of the MCI driver, in
order to configure your microcontroller to access the memory card in SPI mode.
Exercise: MMC-Based File System
This project demonstrates configuration of a memory-card-based file system,
using either a dedicated MCI peripheral or SPI interface.
61
62
Chapter 3. RL-Flash Introduction
Serial Flash
The file system can also be placed on a serial
Flash connected to the SPI port. The same
SPI drivers used for the memory card can be
reused to provide low-level access to the
Flash memory. However, unlike the
SD/MMC memory, there is no common
communication protocol. Therefore, we
need to provide an intermediate driver that
provides the necessary protocol commands to communicate with the SPI
memory.
The protocol file is very similar to the parallel Flash driver files and can be found
in C:\KEIL\ARM\RL\FLASHFS\FLASH.
Here select the directory named after the Flash device you intend to use and copy
the contents to your project directory. The files contained in the device directory
are: FS_SPI_FlashDev.h and FS_SPI_FlashPrg.c.
The FS_SPI_FlashDev.h file contains a description of the physical Flash sectors and
the FS_SPI_FlashPrg.c module contains the erase and programming algorithms
customized to the Flash device. The functions in the FS_SPI_FlashPrg.c file
communicate with the SPI device through the low-level SPI drivers. However,
an additional simple function is required to control the SPI slave select line.
Since the implementation of this function will depend on the microcontroller you
are using and your hardware layout, you will need to implement this function
yourself. The pseudo-code for this function is shown below.
void spi_ss (U32 ss) {
if (ss) {
Set Slave select high
} else {
Set Slave select low
}
}
Getting Started: Building Applications with RL-ARM
63
Chapter 4. RL-TCPnet Introduction
One of the key middleware components in the RL-ARM library is the RLTCPnet networking suite. RL-TCPnet has been specifically written for small,
ARM-based, embedded microcontrollers, is highly optimized, has a small code
footprint, and gives excellent performance. In this chapter, we will first review
the TCP/IP protocol and then examine each feature of RL-TCPnet. Each of the
exercises accompanying this chapter show minimal examples intended to
demonstrate one aspect of RL-TCPnet. Full examples can be found in the board
examples directory C:\KEIL\ARM\BOARDS\\\RL\TCPNET. The
code size for each of these programs is as follows:
Demo Example
ROM Size (KB)
RAM Size (KB)
HTTP Server (without RTX Kernel)
25.6
20.0
Telnet Server
20.4
20.0
TFTP Server
20.6
24.7
SMTP Server
16.7
19.5
DNS Resolver
12.7
19.6
TCP/IP – Key Concepts
TCP/IP is a suite of protocols designed to support local and wide area
networking. In order to build a TCP/IP based application you do not need to
fully understand all the protocols within the TCP/IP stack. However, you do
need to understand the basic concepts in to configure your system correctly.
Network Model
The TCP/IP network model is split into four layers that map on to the ISO sevenlayer model as shown below.
The network access layer consists of:
the physical connection to the network.
the packetizing of the application data for the underlying network.
the flow control of the data packets over the network.
64
Chapter 4. RL-TCPnet Introduction
In a typical microcontroller-based system, this layer corresponds to the Ethernet
MAC with PHY chip and the low-level device driver. The TCP/IP stack handles
the transport and network routing layers.
The network layer handles the transmission of data packets between network
stations using the Internet Protocol (IP). The transport layer provides the
connection between application layers of different stations. Two protocols; the
Transmission Control Protocol (TCP) and the User Datagram Protocol (UDP)
handle this. The application layer provides access to the communication
environment from your user application. This access is in the form of welldefined application layer protocols such as Telnet, SMTP, and HTTP. It is
possible to define your own application protocol and communicate between
nodes using custom TCP and UDP packets.
The main three protocols used to transfer application data are: the Internet
Protocol (IP), the Transmission Control Protocol (TCP), and the User Datagram
Protocol (UDP). A typical application will also require the Address Routing
Protocol (ARP) and Internet Control Message Protocol (ICMP). In order to
reduce the size of a TCP/IP implementation for a small microcontroller, some
embedded stacks only implement a subset of the TCP/IP protocols. Such stacks
assume that communication will be between a fully implemented stack, i.e. a PC
and the embedded node. The RL-TCPnet is a full implementation that allows the
embedded microcontroller to operate as a fully functional internet station.
Getting Started: Building Applications with RL-ARM
Ethernet and IEEE 802.3
Today’s most dominant networking transport layer for local area networks is
Ethernet (or rather Ethernet II to be exact). The Ethernet header contains a
synchronization preamble, followed by source and destination addresses and a
length field to denote the size of the data packet.
The Ethernet data frame is the transport mechanism for TCP\IP data over a Local
Area Network.
The data in the information field must be between 46 and 1500 octets long. The
final field in the data packed is the Frame Check Sequence, which is a Cyclic
Redundancy Check (CRC). This CRC provides error checking over the packet
from the start of the destination address field to the end of the information field.
TCP/IP Datagrams
In Ethernet networks, the Ethernet data packet is used as the physical
transmission medium and several protocols may be carried in the information
section of the Ethernet packet. For sending and receiving data between nodes,
the information section of the Ethernet packet contains a TCP/IP datagram.
The Layer2 frame (Ethernet) encapsulates the TCP/IP datagrams.
Internet Protocol
The Internet Protocol is the basic transmission datagram of the TCP/IP suite. It
is used to transfer data between two logical IP addresses. On its own, it is a besteffort delivery system. This means that IP packets may be lost, may arrive out of
sequence, or may be duplicated.
There is no acknowledgement to the sending station and no flow control. The IP
protocol provides the transport mechanism for sending data between two nodes
on a TCP/IP network.
65
66
Chapter 4. RL-TCPnet Introduction
The IP protocol supports
message fragmentation and reassembly; for a small, embedded
node, this can be expensive in
terms of RAM used to buffer
messages. The IP protocol rides
within the Ethernet information
frame as shown below.
The Internet Protocol datagram
provides station-to-station
delivery of data, independent of
the physical network. It does not
provide an acknowledge or
resend mechanism.
The Internet Protocol Header contains a source and destination IP address. The
IP address is a 32-bit number that is used to uniquely identify a node within the
internet. This address is independent of the physical networking address, in our
case the Ethernet station address. In order for IP packets to reach the destination,
a discovery process is required to relate the IP address to the Ethernet station
address.
Address Resolution Protocol
The Address Resolution Protocol (ARP) is used to discover the Ethernet address
of a station on a local network and relate this to the IP address. ARP can be used
on any network that can broadcast messages. The ARP has its own datagram that
is held within the Ethernet frame.
The ARP protocol provides a
method of routing IP messages
on a LAN. It provides a
discovery method to link a
station Ethernet MAC address to
its IP address.
When a station needs to discover
the Ethernet address of a remote
station, it will transmit a
broadcast message that contains
Getting Started: Building Applications with RL-ARM
the IP address of the remote station. The broadcast message also contains the
local station’s Ethernet address and its IP address. All the other nodes on the
network will receive the ARP broadcast message and can cache the sending
node’s IP and station address for future use. All of the receiving stations will
examine the destination IP address in the ARP datagram and the station with the
matching IP address will reply back with a second ARP datagram containing its
IP address and Ethernet station address.
This information is cached by the sending node (and possibly all the other nodes
on the network). Now, when a node on the LAN wishes to communicate to the
discovered station, it knows which Ethernet station address to use to route the IP
packet to the correct node. If the destination node is not on the local network, the
IP datagram will be sent to the default network gateway address where it will be
routed through the wide area network.
Subnet Mask
A local area network is a defined subnet of a network. Often it uses a specific IP
address range that is defined for use as a private network (for example
192.168.0.xxx). The subnet mask defines the portion of the address used to
select stations within the local network.
The subnet mask is used to
define the station address range
for the local area network.
The subnet mask defines the
network address bits that form
the identity of the local network.
The remaining IP address bits
can be used to assign the address
of nodes on the local network.
By using the subnet mask to
determine the identity of the
local network, any IP datagrams not destined for the local network are forwarded
through the network gateway and then routed through the wider internet. Within
a LAN, each network station must have the same subnet mask and a unique IP
address. These settings may be configured manually on each station. It is
possible to configure the subnet and IP address automatically using a dedicated
protocol.
67
68
Chapter 4. RL-TCPnet Introduction
Dynamic Host Control Protocol DHCP
The DHCP supports automatic allocation of IP addresses and configuration of the
subnet mask within a LAN. A DHCP server must be present within the LAN.
This can run on any station and listens on port 67. When a new station is added
to the network, it will request its network configuration from the DHCP server
before it becomes an active station within the network. The DHCP request
process consists of four stages: discovery, offer, request, and acknowledgement.
An Ethernet station can be assigned automatically an IP
address by a DHCP server. This process consists of four
stages, discovery, offer, request, and acknowledge.
To discover the DHCP server, the new station sends a
UDP broadcast packet with address 255.255.255.255.
When the DHCP receives the DHCP discovery packet, it
will reply back to the new station using the Ethernet
MAC address contained within the discovery broadcast.
In this packet, the DHCP server offers the new station an
IP address. To accept this IP address the new station
replies back with a second broadcast packet. The DHCP
server will send a final acknowledgment packet that
contains the remaining network configuration
information and the lease duration of the IP address.
Internet Control Message Protocol
The Internet Control Message Protocol (ICMP) is mainly used to report errors
such as an unreachable destination or an unavailable service within a TCP/IP
network. ICMP is the protocol used by the PING function that is used to check if
a node exists on a network. The Internet Control Message Protocol must be
implemented in a TCP/IP stack. However, in most embedded stacks only the
PING Echo reply is implemented.
Getting Started: Building Applications with RL-ARM
Transmission Control Protocol
The Transmission Control
Protocol is designed to ride
within the IP datagram data
payload.
The IP packet provides the
transport mechanism across
various networks. The TCP
datagram provides the logical
connection between computers
and the application software.
The TCP can be described as
making a logical circuit between
two applications running on
different computers. The
Internet Protocol uses the address of the destination computer. TCP uses a
source and a destination port, and provides error-checking, fragmentation of large
messages, and acknowledgement to the sender. The TCP acknowledgement and
retransmission mechanism uses a “sliding window” method. These calls for
multiple buffers to hold data that may need to be re-transmitted. It is expensive
in both processing power and user RAM, so it is quite a challenge when
implementing a small TCP/IP stack.
The TCP protocol is transported by the IP protocol
and provides the connection to an application on a
remote station. It supports fragmentation of data
packets, acknowledgement, and resending of lost
error packets.
The TCP port number associates the TCP data with
target application software. The standard TCP/IP
application protocols have “well-known ports” so that
remote clients may easily connect to a standard
service. The device providing the service can open a
TCP port and listen on this port until a remote client
connects. The client is then assigned a port on which
to receive data from the server. This port is known as
an ephemeral port as its assignment only lasts for the duration of the
communication session between the server and client.
69
70
Chapter 4. RL-TCPnet Introduction
User Datagram Protocol
Like the Transmission Control Protocol, the User Datagram Protocol rides within
the data packet of the Internet Protocol. Unlike TCP, UDP provides no
acknowledgement and no flow control mechanisms. UDP can be defined as a
best effort, connectionless protocol and is intended to provide a means of
transferring data between application processes with minimal overhead. It
provides no extra reliability over the Internet Protocol.
Like the Transmission Control Protocol, the User
Datagram Protocol is transported by the Internet
Protocol. Unlike TCP, UDP is a simple, low
overhead protocol that provides an easy method of
communication to a remote application.
Although delivery of data cannot be guaranteed
with UDP, its simplicity and ease of use make it the basis of many important
application protocols such as Domain Name Server (DNS) resolving and Trivial
File Transfer Protocol (TFTP).
Sockets
A socket is the combination of an IP address and a port number. In RL-TCPnet,
support is provided for the most useful TCP/IP-based applications such as web
server, using the Hypertext Transfer Protocol (HTTP) and e-mail, which is
implemented with the Simple Mail Transfer Protocol (SMTP). This means that
you do not need to control individual connections. However, if you do wish to
generate your own custom TCP or UDP frames, a low-level sockets library is
also provided. If you intend designing your own protocol you will need to decide
between UDP or TCP frames. UDP is a lightweight protocol that allows you to
send single frames. It does not provide any kind of acknowledgement from the
remote station. If you want to implement a simple control protocol that will
manage its own send and receive packets then use UDP. TCP is a more
complicated protocol that provides a logical connection between stations. This
includes acknowledgement and retransmission of messages, fragmentation of
data over multiple packets and ordering of received packets. If you need to send
large amounts of data between stations or need guaranteed delivery then use
TCP.
Getting Started: Building Applications with RL-ARM
First Project - ICMP PING
In order to understand how RL-TCPnet works, we will make a simple example
that connects a microcontroller to a LAN. We can then check that it is working
by using the Internet Control Message Protocol (ICMP) to PING the board.
The PING project consists
of the startup code and a
module main.c to hold our
source code. We must then
add the RL-TCPnet library.
Next, add the configuration
file Net_Config.c and the
low-level Ethernet driver
EMAC.c. RL-TCPnet comes with fully configured
Ethernet drivers for a wide range of ARM processor-based microcontrollers.
The configuration file can be found in C:\KEIL\ARM\RL\TCPNET\SRC. The Ethernet
drivers for supported devices are located in C:\KEIL\ARM\RL\TCPNET\DRIVERS.
The net_config.c is a template file that allows us to quickly and easily enable the
RL-TCPnet features that we want to use. For this project, we need to define the
basic network parameters. We can enter a fixed IP address, subnet mask,
network gateway, and DNS servers in the same way that we would configure a
PC for a LAN.
The RL-TCPnet also supports DHCP. If DHCP is enabled, the microcontroller
retrieves its IP, subnet, gateway and DNS addresses from a DHCP server on the
local network.
Whether we use fixed IP addresses or retrieve them from the DHCP server, we
must provide an Ethernet Media Access Controller (MAC) address. This is the
station address for the Ethernet network and it must be unique. During
development, you can use a “made up number”, but when you produce a real
product, it must contain a unique MAC address. This is discussed in more detail
at the end of this chapter.
71
72
Chapter 4. RL-TCPnet Introduction
RL-TCPnet also supports the NetBIOS Frames Protocol. If this is enabled, we
can provide our node with a NetBIOS local host name as well as an IP address.
Finally, we must enable the TCP and UDP protocols. The ICMP just uses UDP,
but as we will be using other application protocols that do use TCP, we will
enable both here.
Getting Started: Building Applications with RL-ARM
Once the RL-TCPnet library has been configured, we need to add the following
code to our application code.
void timer_poll ()
if (100mstimeout)
timer_tick ();
tick = __TRUE;
}
{
{
// RL-TCPnet function
}
int main (void)
{
timer_init ();
init_TcpNet ();
while (1) {
timer_poll ();
main_TcpNet ();
}
}
The main while loop must be a non-blocking loop that makes a call to the RLTCPnet library on each pass. In addition, we must provide a timer tick to the RLTCPnet library. This must use a hardware timer to provide a periodic timeout
tick. The tick period should be around 100ms. If you need a different tick rate,
you should reconfigure the timer and change the timer tick interval in
Net_Config.c.
Once configured, the project can
be built and downloaded into the
microcontroller so that we can
test it on a real LAN.
Exercise: PING Project
This project demonstrates how to configure the RL-TCPnet library to create a
minimal TCP/IP station.
73
74
Chapter 4. RL-TCPnet Introduction
Debug Support
There are two available versions
of the RL-TCPnet: a release
version and a debug version.
The debug version uses the
printf() function to output
network debug messages, which
can be used during development.
By default, the printf() function
uses a debug UART as a
standard I/O channel by calling
the low level driver sendchar().
To use the debug version of the library, you must ensure that the UART is
configured and suitable sendchar() code is provided. It is also important to
remember that the sendchar() routine is typically configured to operate in a
polled mode. This will provide a significant overhead to the operation of the RLTCPnet library. A heavily loaded LAN will generate many debug messages that
may in turn cause the RL-TCPnet library to fail.
Exercise: PING with Debug
This example presents the PING project with the RL-TCPnet debug features
enabled.
Using RL-TCPnet with RTX
Although RL-TCPnet can be used as a standalone C
library, it is also possible to use it with RTX.
When RTX is started, call the init_TCPnet()
function, then create
a task for the TCP
timer tick. Then we
need to create a
second task to call
the RL-TCPnet
library.
Getting Started: Building Applications with RL-ARM
void init (void) __task {
init_TcpNet ();
os_tsk_create (timer_task, 30);
os_tsk_create_user (tcp_task, 0, &tcp_stack, sizeof (tcp_stack));
os_tsk_delete_self ();
}
Since the TCP task has a greater memory requirement than most user tasks, it
must be defined with a custom stack space. The tcp_stack is defined as shown
below:
U64 tcp_stack [800/8];
The timer tick is controlled in its own task. This task is given a high priority and
is set to run at intervals of 100msec.
__task void timer_task (void)
os_itv_set (10);
while (1) {
timer_tick ();
os_itv_wait ();
}
}
{
The main tcp_task calls the RL-TCPnet library and then passes execution to any
other task that is in the READY state. Since this task has no RTX system calls
that will block its execution, it is always ready to run. By making it the lowest
priority task in your application, it will enter the RUN state whenever the CPU is
idle.
__task void tcp_task (void)
while (1) {
main_TcpNet ();
os_tsk_pass ();
}
}
{
Exercise: PING With RTX
This exercise demonstrates the PING project built using RTX.
75
76
Chapter 4. RL-TCPnet Introduction
RL-TCPnet Applications
RL-TCPnet supports a number of standard internet applications. These include
trivial file transfer (TFTP), web server (HTTP), email client (SMTP), telnet, and
domain name server (DNS) client. In RL-TCPnet, each of these applications is
quick and easy to configure, as we shall see in the next section.
Trivial File Transfer
RL-TCPnet includes code to implement a TFTP server. As its name suggests,
TFTP is a simple protocol that was developed originally to transfer program
images into remote devices such as internet routers and diskless terminals. In
comparison, FTP is intended to transfer large files across the internet. The TFTP
protocol is much more suitable for a small, embedded system. Compared to FTP,
it also uses a very small amount of resources.
Adding the TFTP Service
Of all the applications supported
by RL-TCPnet, the TFTP server
is the simplest to configure. The
TFTP server is designed to
integrate with the RL-Flash file
system. It works with any media
type available to RL-Flash
(SRAM, Flash, serial Flash or
SD/MMC). You must configure
RL-Flash as described in Chapter 3. In this section we
will look at configuring the TFTP server to work with
an SD/MMC-based file system. We will take the
SD/MMC-based file system developed in Chapter 3
and add the RL-TCPnet files as shown below.
The TFTP support is enabled in the Net_Config.c file.
Once the TFTP server is enabled, you can adjust its
parameters to meet your requirements.
Getting Started: Building Applications with RL-ARM
This includes:
the number of TFTP clients that can be connected simultaneously,
the inactivity timeout for each client,
the number of retries supported.
TFTP uses UDP rather than TCP as its transport protocol. The use of UDP gives
a significant saving in both code size and SRAM footprint.
Complete the TFTP server by adding a user interface file, TFTP_uif.c, located in
This file provides the TFTP callback functions that
link the server to the file system. We do not need to modify this file to make the
basic TFTP server work. To add special features to the TFTP server, modify
these callback functions.
C:\KEIL\ARM\RL\TCPNET\SRC.
Exercise: TFTP Server
This exercise builds a TFTP server that can be used to upload and download files
to the RL-Flash file system.
HTTP Server
One key TCP/IP applications
supported by the RL-TCPnet
library is a HTTP web server. The
web server can be used to deliver
sophisticated HTML pages to any
suitable web browser running on
any platform, be it a PC, Mac,
smart-phone, or other internet
enabled device. The HTTP server has a Common
Gateway Interface (CGI) that allows us to input and
output data to the embedded C application.
To configure the web server, take the first PING
project and enable the web server option in Net_Config.c.
In the HTTP server section, define the number of web
browsers that can connect simultaneously to the server.
It is also possible to create an access username and
password.
77
78
Chapter 4. RL-TCPnet Introduction
Web Server Content
The content held in the web server can be any file type that can be displayed by a
web browser. This will be hypertext markup language (HTML), which may also
contain images held in any common format such as PNG, GIF, and JPEG, sound
in WAV or MP3 formats, and active content such as Java script libraries. You
are limited only by the amount of storage space available to your microcontroller.
Since this will be quite small compared to a full-scale web server, you should be
careful about which tool you use to generate the HTML script. Tools such as
Dreamweaver or FrontPage are likely to generate complex scripts that will be too
large to store on a small microcontroller. If you are not familiar with HTML,
there are many free tutorials available on the internet. You will also need a
simple HTML editor so that you can design minimal HTML pages. Some
suitable resources are listed in the bibliography section at the end of this book.
Adding Web Pages
Once RL-TCPnet is configured and running on the network, we can start to add
some content to the web server. Generally, this takes the form of HTML pages.
You may start with a simple HTML script like the one below.
…
HTML Example