Warwick A. Smith
Warwick A. Smith lives in
South Africa and works
as an Electronics Engineer
and Embedded System
Programmer. He is a
bestselling author of the
books C Programming for
Embedded Microcontrollers, ARM Microcontroller
Interfacing and Open
Source Electronics on
Linux.
ISBN 978-1-907920-46-2
Arduino is the hardware platform used to teach the C
programming language as Arduino boards are available
worldwide and contain the popular AVR microcontrollers from
Atmel.
Atmel Studio is used as the development environment for
writing C programs for AVR microcontrollers. It is a full-featured
integrated development environment (IDE) that uses the GCC C
software tools for AVR microcontrollers and is free to download.
•
Start learning to program from the very first chapter
•
No programming experience is necessary
•
Learn by doing - type and run the example programs
•
A fun way to learn the C programming language
•
Ideal for electronic hobbyists, students and engineers
wanting to learn the C programming language in an
embedded environment on AVR microcontrollers
•
Use the free full-featured Atmel Studio IDE software for
Windows
•
Write C programs for 8-bit AVR microcontrollers as found
on the Arduino Uno and MEGA boards
•
Example code runs on Arduino Uno and Arduino MEGA
2560 boards and can be adapted to run on other AVR
microcontrollers or boards
•
Use the AVR Dragon programmer / debugger in conjunction
with Atmel Studio to debug C programs
DESIGN
www.elektor.com
Technology is constantly changing. New microcontrollers
become available every year. The one thing that has stayed
the same is the C programming language used to program
these microcontrollers. If you would like to learn this standard
language to program microcontrollers, then this book is for you!
AVR MICROCONTROLLERS AND ATMEL STUDIO FOR
C
PROGRAMMING
WITH ARDUINO
LEARN
Elektor International Media BV
C
PROGRAMMING
WITH ARDUINO
C PROGRAMMING WITH ARDUINO ● WARWICK A. SMITH
AVR MICROCONTROLLERS AND ATMEL STUDIO FOR
Warwick A. Smith
LEARN
DESIGN
SHARE
SHARE
LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● S
● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE
GN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ●
LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ●
● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE
C Programming with Arduino
●
Warwick Smith
an Elektor Publication
LEARN
DESIGN
SHARE
●
This is an Elektor Publication. Elektor is the media brand of
Elektor International Media B.V.
78 York Street
London W1H 1DP, UK
Phone: (+44) (0)20 7692 8344
© Elektor International Media BV 2016
First published in the United Kingdom 2016
159020-1/EN
● All rights reserved. No part of this book may be reproduced in any material form, including photocopying,
or storing in any medium by electronic means and whether or not transiently or incidentally to some other
use of this publication, without the written permission of the copyright holder except in accordance with
the provisions of the Copyright, Designs and Patents Act 1988 or under the terms of a licence issued by the
Copyright Licensing Agency Ltd, 90 Tottenham Court Road, London, England W1P 9HE. Applications for the
copyright holder’s written permission to reproduce any part of this publication should be addressed to the
publishers. The publishers have used their best efforts in ensuring the correctness of the information contained
in this book. They do not assume, and hereby disclaim, any liability to any party for any loss or damage caused
by errors or omissions in this book, whether such errors or omissions result from negligence, accident or any
other cause.
● British Library Cataloguing in Publication Data
Catalogue record for this book is available from the British Library
978-1-907920-46-2
● ISBN
EISBN 978-3-89576-352-6
EPUB 978-3-89576-353-3
Prepress production: DMC ¦ daverid.com
Printed in the Netherlands by Wilco
Elektor is part of EIM, the world’s leading source of essential technical information and electronics products for pro engineers,
electronics designers, and the companies seeking to engage them. Each day, our international team develops and delivers
high-quality content - via a variety of media channels (e.g., magazines, video, digital media, and social media) in several
languages - relating to electronics design and DIY electronics. www.elektor.com
LEARN
DESIGN
SHARE
Table of Contents
Table of Contents
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
Learning C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
Target Audience . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
AVR Microcontrollers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
The AVR Architecture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
Embedded Systems . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
Choosing an Embedded System to Learn C On . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
Why use the C Programming Language? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
Prerequisites . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
Hardware Requirements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
Arduino Board . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
Programmer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
The Approach Taken in this Book . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
Some Good Advice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
Accompanying Files and Support Website . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
Chapter 1 Your First C Program . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
1.1 Download and Install Atmel Studio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
1.1.1 Downloading Atmel Studio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
1.1.2 Installing Atmel Studio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
1.2 Download and Install a Terminal Emulator . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
1.3 Install Template Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
1.4 Connecting the Hardware . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
1.5 How C Programs are Created . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
1.6 Start Programming . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
1.6.1 Creating a New Project from the Template Files . . . . . . . . . . . . . . . . . . . . . 27
1.6.2 Building the Project . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
1.6.3 Build Problems and Solutions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
1.6.4 Loading and Running the Program . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
1.6.5 Program Loading and Running – Problems and Solutions . . . . . . . . . . . . . . 33
1.7 About Your First C Program . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
●5
C Programming with Arduino
1.8 Analysing the Program . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
1.8.1 Characters and Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
1.8.2 Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
1.8.3 Program Statements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
1.8.4 Preprocessor Directives . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
1.8.5 Whitespace Characters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
1.8.6 Ending the Program . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
1.9 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
1.9.1 Three Text Lines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
1.9.2 Find the Errors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
1.10 Solutions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
1.10.1 Solution to 1.9.1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
1.10.2 Solution to 1.9.2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
1.11 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
Chapter 2 C Basics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
2.1 Input, Output and Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
2.1.1 Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
2.1.2 Getting Input from the User Using the scanf() Function . . . . . . . . . . . . . . . 44
2.2 Variable Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
2.2.1 Floating Point Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
2.2.2 Character Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
2.3 Arithmetic Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
2.4 Field Width Specifiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
2.5 Compiling and Linking . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
2.6 Errors and Warnings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
2.6.1 Compile Errors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
2.6.2 Link Errors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
2.7 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
2.7.1 Ohm’s Law Calculations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
2.7.2 Variable Names . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
2.8 Solutions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
2.8.1 Solution to 2.7.1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
●6
Table of Contents
2.8.2 Solution to 2.7.2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
2.9 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
Chapter 3 Comparative Operators and Decisions . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
3.1 Comparative Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
3.1.1 True and False . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
3.1.2 C Comparative Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
3.2 Decisions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
3.2.1 Using if to Make a Decision . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
3.2.2 Using else in Conjunction with if . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
3.2.3 The = Operator and the == Operator . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
3.2.4 Using else – if in Conjunction with if . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
3.3 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
3.3.1 Variable Compare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
3.3.2 Variable Compare 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
3.4 Solutions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
3.4.1 Solution to 3.3.1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
3.4.2 Solution to 3.3.2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
3.5 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
Chapter 4 The while Loop and Commenting Code . . . . . . . . . . . . . . . . . . . . . . . . . . 73
4.1 The while Loop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
4.2 Using if Inside the while Loop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
4.3 The Guess My Number Game . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
4.4 Back to the Temperature Controller Example . . . . . . . . . . . . . . . . . . . . . . . . . . 78
4.5 Commenting Programs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
4.6 Programming Style . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
4.6.1 Tab Settings in Atmel Studio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
4.6.2 Choosing Tab Width . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
4.6.3 Code Indenting Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
4.6.4 Other Aspects of Programming Style . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
4.7 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
4.7.1 Subtract . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
4.7.2 Program Loop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
●7
C Programming with Arduino
4.8 Solutions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
4.8.1 Solution to 4.7.1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
4.8.2 Solution to 4.7.2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
4.9 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
Chapter 5 Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
5.1 Your Second Function . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
5.2 Passing Data to a Function . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
5.3 Passing More Than One Value to a Function . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
5.4 Passing a Variable to a Function . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
5.5 Getting a Value Back from a Function . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
5.6 Passing Values to a Function and Receiving a Value Back . . . . . . . . . . . . . . . . . . 96
5.7 Flashing LED Simulation Program . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
5.7.1 The while(1) Loop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
5.7.2 The long Data Type . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
5.8 Preprocessor Directives . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
5.9 Functions Calling Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
5.10 Using Multiple Source Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
5.10.1 Adding a New C Source File to an Atmel Studio Project . . . . . . . . . . . . . . 104
5.10.2 Project with Two C Source Code Files . . . . . . . . . . . . . . . . . . . . . . . . . . 104
5.11 Header Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
5.12 How Functions Relate to Linking and Library Files . . . . . . . . . . . . . . . . . . . . . 107
5.13 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
5.13.1 Parallel Resistor Calculation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
5.13.2 Resistor Formula C File . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
5.14 Solutions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
5.14.1 Solution to 5.13.1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
5.14.2 Solution to 5.13.2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
5.15 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
Chapter 6 Number Systems . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
6.1 Binary Basics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
6.2 The Need for Binary Numbers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
6.3 Numbering Systems . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
●8
Table of Contents
6.3.1 A Quick Look at Decimal Numbers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
6.3.2 Binary Numbers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
6.3.3 Hexadecimal Numbers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
6.4 Working with Hexadecimal Numbers in C . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
6.5 The ASCII Alphanumeric Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
6.6 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
6.6.1 Hexadecimal to Port . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
6.6.2 Print Number . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
6.7 Solutions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
6.7.1 Solution to 6.6.1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
6.7.2 Solution to 6.6.2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
6.8 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
Chapter 7 Memory and Microcontrollers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
7.1 Memory Basics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
7.1.1 ROM (Read Only Memory) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
7.1.2 RAM (Random Access Memory) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
7.1.3 Data Stored in Memory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
7.2 A Look at a Memory Chip . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130
7.3 How Microprocessors Access Memory and Peripherals . . . . . . . . . . . . . . . . . . . 133
7.4 Pointers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134
7.5 More on C Data Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138
7.6 Microcontroller Memory Sizes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142
7.7 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143
Chapter 8 Accessing AVR Ports in C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
8.1 AVR Pins and Ports . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
8.2 Switching the On-board LED On and Off . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
8.3 Controlling LEDs interfaced to a Port . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152
8.3.1 Connecting the Hardware . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152
8.3.2 Starting a New Atmel Studio Project from Scratch . . . . . . . . . . . . . . . . . . 154
8.3.3 LED Port Control Program . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
8.4 Increment and Decrement Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159
8.4.1 Pre-increment and Pre-decrement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159
●9
C Programming with Arduino
8.4.2 Post-increment and Post-decrement . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159
8.5 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161
Chapter 9 I/O and Memory Maps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
9.1 Input Pins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
9.1.1 Connecting a Switch to a Port Pin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
9.1.2 Reading the State of a Switch in C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164
9.2 Input and Output Pins on the Same Port . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166
9.2.1 Connecting a Switch and LED . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166
9.2.2 Programming a Switch and LED . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166
9.2.3 The AND Bitwise Operator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167
9.3 AVR Memory Map . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169
9.3.1 Memory Architecture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 170
9.3.2 Flash Program Memory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 170
9.3.3 Data Memory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 170
9.4 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 170
Chapter 10 Previous C Topics Revisited . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173
10.1 Format Specifiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173
10.2 Field Width Specifiers Revisited . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174
10.3 Escape Sequences . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176
10.4 Loops . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 178
10.4.1 A while Loop that uses break and continue . . . . . . . . . . . . . . . . . . . . . . 178
10.4.2 The do – while Loop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180
10.4.3 The for Loop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181
10.5 The Problem with printf() in Embedded Systems . . . . . . . . . . . . . . . . . . . . . . 182
10.5.1 Programming the Serial Port . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 182
10.5.2 Writing Serial Port Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184
10.6 Nested Loops and Decisions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188
10.6.1 Nested Loops . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188
10.6.2 Nesting Decisions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 189
10.7 Decision Making with the switch Statement . . . . . . . . . . . . . . . . . . . . . . . . . . 190
10.8 The Conditional Operator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193
10.9 Functions and Pointers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 194
● 10
Table of Contents
10.9.1 Passing an Address to a Function . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 194
10.9.2 Returning More Than One Value from a Function . . . . . . . . . . . . . . . . . . 195
10.10 Variables and Scope . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196
10.10.1 Local Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196
10.10.2 Global Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197
10.11 Static Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 198
10.12 Floating Point Data Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199
10.13 Casts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 200
10.14 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201
10.14.1 Count Down for Loop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201
10.14.2 LED Control using a switch Construct . . . . . . . . . . . . . . . . . . . . . . . . . 201
10.15 Solutions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201
10.15.1 Solution to 10.14.1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201
10.15.2 Solution to 10.14.2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 202
10.16 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 202
Chapter 11 Arrays and Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205
11.1 Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205
11.2 Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209
11.2.1 Writing to a String . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 210
11.2.2 Initialising a String . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 212
11.2.3 C Library String Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213
11.3 Arrays and Addresses . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214
11.3.1 Arrays and Array Element Addresses . . . . . . . . . . . . . . . . . . . . . . . . . . 214
11.3.2 Passing an Array to a Function . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 216
11.4 Strings as Pointers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 218
11.5 Writing to a String using sprintf() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 219
11.6 Multidimensional Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 221
11.7 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 226
11.7.1 Button Message . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 226
11.7.2 Button Wait . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 227
11.8 Solutions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 227
11.8.1 Solution to 11.7.1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 227
● 11
C Programming with Arduino
11.8.2 Solution to 11.7.2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 227
11.9 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 228
Chapter 12 Bit Manipulation and Logical Operators . . . . . . . . . . . . . . . . . . . . . . . . 229
12.1 Bit Manipulation with Bitwise Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . 229
12.1.1 Why Do We Need Bitwise Operators? . . . . . . . . . . . . . . . . . . . . . . . . . . 231
12.1.2 Using the Bitwise AND and OR Operators . . . . . . . . . . . . . . . . . . . . . . . 232
12.1.3 Using the Bitwise NOT Operator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 234
12.1.4 Using the Bitwise XOR Operator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235
12.1.5 The Left and Right Shift Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . 236
12.1.6 The C Assignment Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238
12.2 Logical Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238
12.3 Operator Precedence . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241
12.4 Using Pre-defined Bit Names with Shift Operators . . . . . . . . . . . . . . . . . . . . . 243
12.4.1 Pre-defined Bit Names in C Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 243
12.4.2 The AVR _BV() Macro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 245
12.5 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 247
12.5.1 Print Binary Number . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 247
12.6 Solution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 248
12.6.1 Solution to 12.5.1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 248
12.7 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 248
Chapter 13 Programming Hardware . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251
13.1 Timer Counter Hardware . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251
13.1.1 Polled Timer Time Delay . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 252
13.1.2 Hardware Timer Function . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 255
13.2 The Analogue to Digital Converter (ADC) . . . . . . . . . . . . . . . . . . . . . . . . . . . 256
13.3 The Watchdog Timer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 260
13.4 I/O Ports . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 262
13.4.1 I/O Port Register Functionality . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 262
13.4.2 Register Pin Toggle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 262
13.4.3 Enabling Pull-up Resistors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 263
13.5 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 265
Chapter 14 Debugging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 267
● 12
Table of Contents
14.1 Configuring Arduino Boards for Debugging . . . . . . . . . . . . . . . . . . . . . . . . . . 267
14.2 Debugging Example Program . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 267
14.3 Switching Compiler Optimisation Off . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 269
14.4 Enabling debugWIRE on the AVR . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 269
14.5 Using the Debugger . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 271
14.5.1 Starting the Debugger . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 271
14.5.2 Single Stepping and Adding Watches . . . . . . . . . . . . . . . . . . . . . . . . . . 271
14.5.3 Viewing Hardware Registers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 271
14.5.4 Viewing and Modifying Variable and Register Values . . . . . . . . . . . . . . . . 273
14.5.5 Viewing Variables in Memory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 273
14.5.6 Stopping Debugging and Setting Breakpoints . . . . . . . . . . . . . . . . . . . . 274
14.5.7 Viewing Pin State Changes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 275
14.5.8 Viewing Variable Value Changes & Setting Conditional Breakpoints . . . . . 276
14.5.9 Stepping into a Function . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 277
14.6 Enabling ISP on the AVR / Disabling debugWIRE . . . . . . . . . . . . . . . . . . . . . . 277
14.7 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 277
14.8 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 278
Chapter 15 Interrupts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 279
15.1 Handling Interrupts in C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 279
15.2 Using the Timer Interrupt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 279
15.3 One Millisecond Tick Interrupt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 281
15.4 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 284
Chapter 16 Wrapping Up the C Language . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285
16.1 Structures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285
16.1.1 Using Structures in C Programs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285
16.1.2 Initialising Structures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 287
16.1.3 Pointers to Structures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 288
16.2 Unions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 288
16.3 Enumerated Type . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 291
16.4 The typedef Declarator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 293
16.5 Storage Class Specifiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 294
16.6 Type Qualifiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 295
● 13
C Programming with Arduino
16.7 The goto Statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 295
16.8 A List of All C Keywords . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 296
16.9 More Preprocessor Directives . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 296
16.10 C99 Variable Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 298
16.11 Alternative Continuous Loop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 298
16.12 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 299
Chapter 17 C Projects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 301
17.1 Serial Port Driver Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 301
17.1.1 Serial Port Driver Design Notes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 301
17.1.2 Serial Port Driver Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 302
17.2 Converting Numbers to Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 306
17.2.1 Using AVR C Library Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 306
17.2.2 Writing Number to String Conversion Functions . . . . . . . . . . . . . . . . . . . 309
17.3 Voltmeter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 314
17.3.1 Voltmeter Project Features . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 314
17.3.2 Voltmeter Project Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 314
17.3.3 Enabling the printf Family of Functions to Work . . . . . . . . . . . . . . . . . . . 317
17.3.4 Principle of Operation of the Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . 317
17.3.5 Examining the Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 318
17.3.6 Code Timing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 319
17.4 Stopwatch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 320
17.4.1 Stopwatch Project Hardware and Code . . . . . . . . . . . . . . . . . . . . . . . . . 320
17.4.2 Stopwatch Project Design . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 323
17.4.3 Stopwatch Project Code Explanation . . . . . . . . . . . . . . . . . . . . . . . . . . . 323
17.5 Where to from here? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 327
Appendix A The ASCII Table . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 329
Appendix B Arduino – AVR Port Pin Mapping . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 331
B.1 Arduino Uno . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 331
B.2 Arduino MEGA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 332
Appendix C Standard Arduino I/O Circuit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 335
C.1 Arduino Uno . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 335
C.2 Arduino MEGA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 336
● 14
Table of Contents
Appendix D References and Resources . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 337
D.1 Book Website and Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 337
D.2 Datasheets and Application Notes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 337
D.3 AVR Libc Reference . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 337
D.4 AVR Forum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 337
D.5 Additional Resources . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 337
Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 339
● 15
C Programming with Arduino
● 16
Introduction
Introduction
This book will teach you the C programming language using AVR microcontroller based
Arduino boards, such as the Uno and MEGA, as a teaching platform. Atmel Studio for
Windows operating systems is the IDE and software tool used in this book for writing,
compiling and loading C programs to the Arduino boards.
Atmel Studio and the standard C language are used as an alternative to the Arduino
IDE which was designed to help those with less technical knowledge of electronics and
microcontrollers to get started with microcontroller projects. Standard C is taught by
Universities as part of electronic engineering courses and is used throughout industry for
programming microcontrollers. C programming can be used on any microcontroller for
which C programming tools are available and is not restricted to Arduino. Arduino is used
with this book for teaching C as it is very convenient to use and readily available.
Learning C
Learning a new programming language and learning how to program embedded systems
covers a lot of ground as there are many things to learn: the C programming language
itself, some knowledge of microcontroller architecture, some electronics, microcontroller
peripherals, number systems and logic. There is also the software programming toolchain
and IDE to learn, which in the case of this book is the Atmel Studio IDE that uses the
GNU toolchain for AVR microcontrollers. It may seem to be a daunting task to learn about
all these aspects of an embedded system and programming language, but taking it one
step at a time as this book does makes it easy, enjoyable and a lot of fun.
Technical Terms
IDE – Integrated Development Environment. An IDE is software that provides a complete
development environment for writing and editing programs, compiling, loading programs
to the target board and debugging.
Toolchain – the C toolchain is a set of software programs that are used to convert a C
program into a file that can be loaded to a microcontroller.
Atmel Studio – an IDE that runs on Windows operating systems and is used to program
Atmel AVR and other Atmel microcontrollers. Atmel Studio can be downloaded for free
from the Atmel website.
Target Audience
This book has been written for the hobbyist, student and engineer wanting to learn the C
programming language in an embedded environment using microcontrollers.
AVR Microcontrollers
AVR microcontrollers, the microcontrollers found on Arduino boards such as the Uno and
MEGA are used in this book. The C language applies equally to other microcontrollers, but
when learning to program, a particular microcontroller architecture must be chosen as
hardware differences between microcontrollers will change how they are programmed.
The AVR Architecture
AVR microcontrollers from Atmel (www.atmel.com) are RISC microcontrollers available
● 17
C Programming with Arduino
in 8-bit and 32-bit series. These microcontrollers contain a “AVR core” that is interfaced
to memory and peripheral devices and packaged in a single chip. Only 8-bit AVR
microcontrollers are presented in this text.
Technical Terms
RISC – RISC stands for Reduced Instruction Set Computer and is a processor that is
designed with a simplified instruction set in order to produce higher performance. The
AVR architecture has been designed this way.
Atmel AVR Microcontrollers
Embedded Systems
An embedded system is an electronic circuit board that contains a microprocessor or
microcontroller and other electronic parts, running a software program and used for a
specific purpose rather than a general purpose as a Personal Computer (PC) would be
used.
Technical Terms
Microprocessor – A microprocessor is a silicon chip, also known as an Integrated Circuit
or I.C., containing a Central Processing Unit (CPU) that will fetch program instructions
from memory and execute or run them. The memory required by the microprocessor is
external to the microprocessor chip – it must be put on the circuit board by the hardware
engineer designing the system, whether it is to be used as an embedded system or
general purpose computer. There are a number of microprocessor architectures such as
Intel’s x86 architecture, ARM architecture, AVR from Atmel and many others.
Microcontroller – A microcontroller contains a microprocessor and a number of
peripheral devices such as timers, serial ports, general purpose input/output (I/O) pins,
counters, analogue inputs, etc. all inside a single silicon chip. A microcontroller will usually
have memory on the chip as well. Some microcontrollers will be able to support external
memory and others have enough memory on-chip and do not have the ability to support
external memory. Microcontrollers are designed to be used in embedded systems as their
on-chip peripherals and memory enable an embedded system designer to save circuit
board space by not having to add these items as external devices on the circuit board.
● 18
Introduction
An example of an embedded system is a calculator. It is specifically made to perform
mathematical calculations input by a user on the keypad – it is a dedicated device. A PC
on the other hand is a general purpose computer that can be used for any number of
purposes such as word processing, playing games, a file server, web server, etc. (i.e. not
an embedded system).
Other examples of embedded systems are cell phones and computer keyboards as
they contain embedded microcontrollers that give these devices their functionality.
Smartphones, however, have become more like PCs than dedicated embedded systems,
even running their own operating systems. A washing machine may contain an embedded
system that responds to button presses on its front panel and controls washing cycles.
Although Arduino boards are general purpose boards that can be used for many different
tasks, they are still embedded systems as their end use is for a specific task, such as a
temperature monitoring system, animated robot, chocolate dispensing machine, or any
other project that you can think of.
Abbreviations
Microprocessor – the word microprocessor is often abbreviated to μP.
Microcontroller – the word microcontroller is often abbreviated to μC or MCU.
μ – is the symbol for “micro” and is also used in electronic or other engineering
measurements such as microfarad μF for capacitor values.
Choosing an Embedded System to Learn C On
Arduino has been chosen as the embedded system for this book, and specifically Arduino
boards that have 8-bit AVR microcontrollers on-board and can be fitted with the standard
add-on boards called shields.
Arduino has been chosen because it is so widely available, is an open source project
and has many second-source compatible boards available. Before Arduino became
popular, a specific proprietary board would have to be recommended for any embedded
programming book. With Arduino we now have standard off-the-shelf hardware that is
open source, allowing anyone to view and modify the circuit diagram and board.
Here are some of the reasons that Arduino was chosen:
•
Powered by USB – no external power supply needed
•
Easy to connect hardware using an electronic breadboard and jumper wires
•
USB port on-board that is configured as a virtual COM port on the PC for serial
communications
•
Widely available with alternative sources and clone boards / derivative boards
•
Has ICSP header for programming and debugging
In the pages that follow, you will learn to write programs that run on an Arduino board
or embedded system and switch LEDs on and off, read switches to see if they are being
pressed or released, send messages out of the serial port to be displayed on a PC and
more.
● 19
C Programming with Arduino
Arduino MEGA 2560 (left) and Arduino Uno (right)
Why use the C Programming Language?
If I think of all the technology changes that have taken place in the world of
microelectronics since the start of my career as an embedded programmer, the one
thing that has stayed the same is the C language. Processor architectures and systems
have come and gone, but the common thing between all of them is that they were
programmed in C.
The reason for the popularity and use of C is that C is a standard language and ideally
suited to embedded programming where access to the hardware of a system is required.
C programming tools produce small and fast programs – a requirement when you
have limited amounts of memory available on an embedded system. Whenever a new
processor architecture comes on the market, you can be pretty sure that there will be C
programming tools available for it.
C programming is necessary to move beyond Arduino in order to program other
embedded systems that are not supported by the Arduino IDE, as well as gain more
control over Arduino boards.
You may have heard that C is cryptic and difficult to learn, but this is nonsense. C is a
simple language with very few keywords and is easy to learn. As with any new language,
you need to become familiar with the syntax of the language. This book will explain
everything to you, starting with simple and easy programs. When I leaned to program in
C, I thoroughly enjoyed it and I hope that you will too.
Prerequisites
In order to use this book you will need to be able to operate a computer running Windows
and know how to download and install software on a Windows system.
Any knowledge of electronics will help, for example you should know what an LED is, a
switch, I.C. and a resistor. I will refer to basic electronic components such as these in
the text. If you don’t know what these items are, there are plenty of books available that
teach basic electronics. A good online resource for basic electronics, breadboard circuits
and an introduction to Arduino is: startingelectronics.org/beginners/start-electronics-now/
Any knowledge of programming in any language will help, but is not required.
Basic mathematics is also assumed. i.e. addition, subtraction, multiplication, division,
counting and a little algebra. If you are not good at mathematics, that is not a problem as
the mathematics is very basic and the programs that you will write will actually help you
to understand mathematics better.
● 20
Introduction
You will need to have an Internet connection in order to download software programs and
example programs used with this book (all software used is free).
Hardware Requirements
A standard USB cable will be needed for powering the Arduino board and communicating
between the Arduino and PC. An external power supply can be used to power the Arduino
board, but a USB cable will still be needed for serial communications with the PC.
Electronic components such as LEDs, resistors and switches will be needed and are listed
in each section of the book when they are required.
When using the Atmel Studio IDE, a programming device is needed to load programs to
the microcontroller on the Arduino board. The programmer is connected to the PC using a
USB cable and to the 6-pin header on the Arduino board labelled ICSP. More details of the
Arduino board and programmer required can be found below.
Arduino Board
An Arduino Uno with an Atmega328P microcontroller or Arduino MEGA 2560 will be
needed to follow the example programs in this book. An Arduino Uno R3 and Arduino
MEGA 2560 R3 were used when developing the code examples.
If you have not yet bought an Arduino board, I would recommend getting the Arduino
MEGA 2560 as it has more memory and input / output pins than the Uno.
Atmel AVRISP mkII Programmer and Arduino Uno
● 21
C Programming with Arduino
Programmer
An AVRISP mkII programmer (part number ATAVRISP2) or AVR Dragon programmer /
debugger (part number ATAVRDRAGON) is required for loading programs to the Arduino
board. Both of these programmers are Atmel devices and are fully compatible with Atmel
Studio.
I would recommend getting the AVR Dragon as it has debugging capabilities as well
as the ability to do HV programming that can be used to unbrick a DIP packaged AVR
microcontroller. The AVRISP mkII can only program AVR microcontrollers and has no
debugging capabilities. The AVR Dragon does not come with any cables, you will need a
standard USB cable as well as a 6-way ribbon cable with female 6-way (2 by 3) headers
on each end.
There are other AVR programmers on the market made by various manufacturers that
may be cheaper than the programmers from Atmel, but these programmers will need
external software in order to use them. They will also require extra set-up steps in order
to use them in Atmel Studio and will most likely not have any debugging abilities.
Atmel AVR Dragon Programmer / Debugger and Arduino Uno
Technical Terms
Debugging – Debugging refers to finding and fixing program errors. An embedded
programmer or software engineer will write a program and then test it to see if it works
as intended. If there is a problem with the program, e.g. “Why does the LED not switch
on when it is supposed to?”, the embedded programmer will debug the program.
AVR microcontrollers on the Arduino boards used in this book can be debugged using
the AVR Dragon attached to the ICSP header on the board. When a software program
called a debugger is run on the PC from within Atmel Studio, it accesses the embedded
system through the ICSP header via the AVR Dragon and enables the programmer to step
through the program and examine the contents of the microcontroller’s memory. This
helps the programmer to find the bug (program error) and fix it.
● 22
Introduction
The Approach Taken in this Book
This book uses free or open source software only. You will start learning to program right
from chapter 1 after installing and setting up programming tools.
The first 7 chapters of the book teach the basics of the C language, concentrating on the
language by sending output from C programs running on an Arduino to the PC for display
in a terminal window, and receiving data from the terminal window typed in by a user.
Chapter 8 and beyond builds on the C basics already learned in the first chapters and
starts to look at programs that use the microcontroller’s peripherals for more practical
embedded applications.
Some Good Advice
Take your time to learn and understand how the programs work before moving to the
next chapter. Experiment with the code by changing it to see what happens and try to
write your own programs, or modified versions of the example programs to aid in learning
as you progress through the book.
If you can’t type, learn to type while learning C. This is what I did when learning to
program and have never regretted it. I always entered the example programs by typing
them in rather than just running the downloaded example programs from disk.
Learning C and embedded systems is great fun and a big adventure, so it is my wish that
you will thoroughly enjoy using this book and learning new things.
Accompanying Files and Support Website
An accompanying zipped file is available for download from Elektor that contains all of
the C source code for the example programs in this book. Download the accompanying
files from the Elektor website at www.elektor.com by browsing for the book from the books
menu or using the search box to find the book’s page.
An accompanying website can be found at wspublishing.net/avr-c that contains extra
articles and information related to this book and will be updated with any errata found.
● 23
C Programming with Arduino
● 24
Chapter 1 • Your First C Program
Chapter 1 • Your First C Program
What you will do in this chapter:
•
Download and install Atmel Studio
•
Download and install a terminal emulator program
•
Install template files to make creating new projects easier
•
Connect the Arduino board and programmer to a PC
•
Write your first C program
•
Compile, load and run your first program
1.1 • Download and Install Atmel Studio
Downloading and installing Atmel Studio is straightforward and should not pose any
problems. Brief download and installation instructions are presented below. More detailed
instructions with screen captures can be found on the supporting website at:
wspublishing.net/avr-c.
1.1.1 • Downloading Atmel Studio
Atmel Studio can be downloaded by visiting the Atmel Studio web page and scrolling
down the page to find a link to the Atmel Studio installer.
Atmel Studio web page: www.atmel.com/tools/atmelstudio.aspx
Two downloads of Atmel Studio are available – a web installer version and an offline
installer version. I would suggest downloading the offline installer version which will allow
Atmel Studio to be installed to any PC whether it is connected to the Internet or not.
Figure 1-1 Download Atmel Studio
Click the image of the disk to the left of the chosen version of Atmel Studio (Figure 1-1)
and you will be taken to a new web page. You can now either create an Atmel account,
sign in with an existing Atmel account, or download the file as a guest. To download
Atmel Studio as a guest, enter your details and a valid email address. A link to the file to
download will be emailed to you, which you can click to open a page that contains a link
to the Atmel Studio installation file. Click the link to start the download.
The advantage of creating an Atmel account is that after Atmel Studio is installed,
updates and addons for Atmel Studio can be installed which require signing into an Atmel
● 25
C Programming with Arduino
account. If you do the download as a guest, you can always create an Atmel account
later.
When the download has finished, a file with a name such as as-installer-7.0.582-full.exe
will be found in the download folder. The exact file name may be different, depending on
whether the downloaded file is a newer version or not.
1.1.2 • Installing Atmel Studio
To install Atmel Studio, simply double-click the downloaded file to run it, which will start
the installation. You will need to agree with the license to be able to continue with the
installation. Do a standard full installation and leave the installation folder names at their
defaults.
1.2 • Download and Install a Terminal Emulator
A terminal emulator program called Tera Term is used for program input / output between
a PC and Arduino board using a USB cable. Tera Term can be downloaded from:
en.osdn.jp/projects/ttssh2/releases/
Download the zipped Tera Term file, which was at version 4.88 at the time of writing, and
named teraterm-4.88.zip containing a folder called teraterm-4.88.
It is not necessary to install Tera Term, simply copy the teraterm-4.88 folder from the
zipped file to any convenient location on the PC, e.g. to the desktop. Double-click the file
called ttermpro.exe in the teraterm-4.88 folder to run Tera Term.
It is best to enable Windows to show file extensions in the file manager to more easily
identify file types. Enabling file extensions to be visible will show the .exe extension of
ttermpro.exe as well as the .c extension of C program files.
Displaying File Name Extensions in Windows 7 File Manager
To display file name extensions in file manager, first open file manager and then click
Organize → Folder and search options to pop up the Folder Options dialog box. In
the dialog box, click the View tab. In the Advanced settings box, uncheck the Hide
extensions for known file types box. Click the OK button in the dialog box to save the
changes.
1.3 • Install Template Files
Included with the accompanying download from Elektor are two template files used to
start new projects in Atmel Studio called template_mega_dragon.zip and template_
uno_dragon.zip found in the Templates folder of the download file. To install these
template files, open your Documents folder and then Atmel Studio → 7.0 → Templates
→ ProjectTemplates and copy the files into the ProjectTemplates sub-folder.
If you are using a newer version of Atmel Studio, substitute the new version number in
place of 7.0 in the above path.
The full path to the ProjectTemplates folder is:
C:\Users\<your user name>\Documents\Atmel Studio\7.0\Templates\
ProjectTemplates
● 26
Chapter 1 • Your First C Program
1.4 • Connecting the Hardware
Use a standard USB cable to connect the AVRISP programmer or AVR Dragon
programmer / debugger to the PC that has Atmel Studio loaded on it. Drivers for the
programming device will automatically be installed.
Connect the 6-way ribbon cable from the programming device (AVRISP or Dragon) to
the 6-pin header labelled ICSP on the Arduino board. Don't connect the programmer to
the 6-pin header that is near the USB connector on the Arduino. On the AVR Dragon, the
6-way ribbon cable is connected to the 6-pin header labelled ISP. Pin 1 of the ISP header
on the Dragon is marked with a 1. Pin 1 of the ICSP header on the Arduino board is
marked with a small dot.
Connect the Arduino board to a second USB port on the PC using a second standard USB
cable. On a Windows 7 system the Arduino drivers will need to be installed if the Arduino
has never been programmed using the Arduino IDE software on the PC. The drivers will
enable the Arduino to be seen as a virtual COM port on the PC. A Windows 10 system
should automatically configure the Arduino as a virtual COM port. For more detailed
hardware connection instructions, see the supporting website at: wspublishing.net/avr-c
1.5 • How C Programs are Created
C programs are written using a text editor and saved to a file with a .c file extension e.g.
myprog.c. When the C compiler is run it reads the C text file, known as the C source file,
and creates an executable file that can be loaded to and run on the embedded system.
The text editor in Atmel Studio has "syntax highlighting" which will highlight C language
keywords making the program a lot easier to read.
1.6 • Start Programming
With the software tools installed and hardware connected, you are now ready to write
your first C program. Start by creating a new sub-folder in which to store the C programs
that you will write when following the examples in this book. The sub-folder can be
created in Documents → Atmel Studio or Documents → Atmel Studio → 7.0 assuming
that the version of Atmel Studio that you are using is version 7.0. A sub-folder called c_
book in Documents → Atmel Studio → 7.0 will be used in the text when describing how
to create new Atmel Studio projects.
You may want to install the code examples for the book that are included with the
accompanying files downloaded from Elektor, so that you can check your code against the
examples. Copy the Arduino_C folder from the accompanying zipped file to Documents
→ Atmel Studio.
1.6.1 • Creating a New Project from the Template Files
When writing a new C program using Atmel Studio, a new project must be created that
will contain the C code as well as other files that store the project settings, such as the
part number of the target microcontroller, which programming device is being used and
various settings for the C toolchain.
Start Atmel Studio, which can easily be found by clicking the Windows start button and
typing "Atmel", or by double-clicking the Atmel Studio icon on the desktop.
A new project can be created by clicking the New Project... item on the Start Page in
Atmel Studio. If the Start Page is not visible, use the top menu to open it by clicking
● 27
C Programming with Arduino
View → Start Page. Alternatively click the New Project icon on the top left toolbar or use
File → New → Project... from the top menu.
In the New Project dialog box that appears (Figure 1-2), select template_mega_dragon
if you are using the Arduino MEGA or template_uno_dragon if you are using the Arduino
Uno. If the template files do not appear in the dialog box, then refer to section 1.3 on
page 26 which explains how to install the templates.
Figure 1-2 The New Project Dialog Box
The name of the first project is hello, so remove the text in the Name: field of the dialog
box and type hello in its place. Use the Browse... button to navigate to the sub-folder
that you created earlier to store your new projects in. Figure 1-3 shows the changes
made to the bottom of the dialog box for the first project.
Figure 1-3 Naming the Project and Selecting the Destination Folder
Click the OK button when the project name and destination have been changed. A new
project will be created in its own folder inside the selected destination folder.
In the right panel of Atmel Studio, click the tab called Solution Explorer to display the
● 28
Chapter 1 • Your First C Program
project's files as shown in Figure 1-4.
Figure 1-4 Select the Solution Explorer Tab
Atmel Studio should now have the main.c file open with some skeleton code displayed as
shown in Figure 1-5. If the main.c file is not open, double-click it in the Solution Explorer
pane to open it for editing.
Figure 1-5 The hello Project Created from the Template File
● 29
C Programming with Arduino
Edit the file by typing in the code from the hello project main.c listing below. After editing
the file, save it by pressing Ctrl + S on the keyboard or by clicking the Save icon on the
top toolbar. When editing the file, be careful not to leave anything out when typing the
program and be sure to type everything exactly as it is shown in the code listing. Starting
the project using the template file makes it necessary to add only one line of code to the
C source file, the line starting with printf.
Project name: hello
Project location: CH1\hello
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
UartInit();
printf("Hello, world!\r\n");
}
while(1) {
}
Program Output
Hello, world!
The line starting with printf, and the line of code above and below it are moved to the
right in the editor, which is known as indenting. Lines of code are indented by pressing
the Tab key which moves the code to the right, although this should not be necessary
when starting the project with the template. Once a line of code has been indented using
the Tab key, lines of code following are automatically indented by the Atmel Studio editor.
Figure 1-6 on page 31 shows the C file in Atmel Studio after editing.
About the Code Listings
Each code listing in this book, like the one above, shows the project name, project
location and C file name above it.
Project name – In Atmel Studio a new project must always be created before a C
program can be written. Every C program is created in a project and this is the project
name displayed above the code listing.
Project location – The project location above the code listing is the location of the
finished project found in the Arduino_C folder of the accompanying files. As an example,
the above project can be found in Arduino_C\CH1\hello with the accompanying files.
C file name – Each C file has a unique name and is usually named main.c in this book,
which is the default name given to the initial C file in Atmel Studio 7, although it could
be given a different name as it does not have to be named main.c. Some projects will
contain more than one C file, each with its own name. There will always be a file that
contains the function main(), explained later in this chapter, and it makes sense to name
the file that contains it main.c so that it can be easily found in a project that contains
many files.
● 30
Chapter 1 • Your First C Program
You will have noticed when typing the program into the Atmel Studio editor that certain
words change colour. This is known as syntax highlighting and makes your program
easier to read by highlighting keywords, data types and other elements of the C
language.
Figure 1-6 The hello Project's main.c File After Editing
1.6.2 • Building the Project
Now that the C source code file hello.c has been edited, the project can be built. The
build process converts the code from the hello.c file into a file that can be loaded to the
microcontroller on the Arduino board or other embedded system.
To build the project, click the Build icon on the toolbar as shown in Figure 1-6. It may be
easier at first to click Build → Build hello on the top menu where you will be able to get
a better view of what the Build icon looks like. Clicking the Build Solution icon next to the
Build icon will also build the project. Each Atmel Studio project is created in a solution,
and a solution can contain more than one project, so building the solution will build all
projects in the solution. In most cases only one project will be created per solution.
1.6.3 • Build Problems and Solutions
Not much can go wrong with the build when using the template file. If an error message
appears at the bottom of the Atmel Studio window, it will probably be because the line of
code that was added is missing something like the semicolon at the end of the line or the
closing quotation marks.
1.6.4 • Loading and Running the Program
The template used for creating the project has been set up with the AVR Dragon as
the programming tool and appears at the right side of the second toolbar as shown in
● 31
C Programming with Arduino
Figure 1-7. If you are using the AVRISP mkII or the AVR Dragon does not appear on the
toolbar, click the tool on the toolbar or the text "No tool" to change it to the AVRISP or to
add the AVR Dragon if it is missing. Make sure that the AVRISP or Dragon is plugged into
the PC before clicking the tool icon. In the dialog box that pops up (Figure 1-8, select the
AVRISP or Dragon from the drop-down box and ISP for the interface. After selecting the
correct programming tool, save the settings by clicking the Save icon or with Ctrl + S
from the keyboard.
Figure 1-7 Microcontroller and Programmer Selection
Figure 1-8 Selecting a Programmer in Atmel Studio
As soon as the program is loaded to the Arduino it will start running and the text message
that the hello.c program prints will be sent out of the microcontroller serial port to the PC
via USB. In order to see the message, the terminal emulator program must be started
and connected to the Arduino USB port before loading the program to the Arduino.
Find the folder containing Tera Term that you copied to your PC in section 1.2 on page
26 of this chapter and double-click the ttermpro.exe file to run Tera Term. In the Tera
Term new connection dialog box, click the Serial radio button and select the COM port
that your Arduino appears as, as shown in Figure 1-9 on page 33,and then click the
OK button.
The default communications settings of Tera Term should be correct for communicating
with the Arduino, but can be checked by selecting Setup → Serial port... from the Tera
Term menu. In the serial port setup dialog box, the serial communications settings should
be as follows:
● 32
Chapter 1 • Your First C Program
Port
– the COM port that your Arduino appears as
Baud rate
– 9600
Data
– 8 bit
Parity
– none
Stop
– 1 bit
Flow control
– none
Figure 1-9 Selecting the Arduino in Tera Term
With the programmer and Tera Term connected to the Arduino, we are ready to load the
program. Click the Start Without Debugging icon on the top toolbar to load the program
to the Arduino as shown in figure 1.8. This is the green triangle arrow icon on the top
toolbar and not the green triangle arrow on the second toolbar. The icon can also be
found on the top menu by clicking Debug → Start Without Debugging – be sure not to
select Continue from this menu.
If the program loaded successfully, the text "Hello, world!" will appear in the Tera Term
window.
1.6.5 • Program Loading and Running – Problems and Solutions
If anything went wrong when trying to load the program to the Arduino, then here are a
few things to check. The programmer (AVRISP or Dragon) must be connected to the PC
via a USB cable and must be shown on the toolbar as seen in Figure 1-7 on page 32.
If the programmer name appears on the toolbar, but the programmer does not work, try
selecting it again by clicking the programmer icon on the toolbar and using the drop-down
list in the page that appears (Figure 1-8 on page 32). Be sure to save after changing
the programmer on the page.
The Arduino board must be powered in order to program it. It can be powered from the
● 33
C Programming with Arduino
USB cable that is also used to communicate with the terminal emulator program.
Tera Term must be connected to the correct COM port that the Arduino appears on. The
Arduino port will only appear in Tera Term if it is plugged into the PC's USB port and the
driver for it is installed.
The correct microcontroller part number must be selected which should be the case if the
project was created from the correct template file. The microcontroller that appears on
the toolbar (Figure 1-7 on page 32 shows the ATmega328P) should be as follows:
•
ATmega328P for all modern Arduino Uno boards
•
ATmega2560 for the Arduino MEGA 2560
1.7 • About Your First C Program
The program that you have written is a classical first program for any computer language
– printing "Hello, world!".
The first thing that you should know about C is that it is case sensitive. If you change the
case of any of the keywords or function names in your program and try to compile it, you
will get an error message. In the hello.c program printf is a function name and while is
a key word. If you typed Printf or While or WHILE for example, the compiler will display
an error message and will not be able to compile the program (the compiler is run as part
of the build process).
Try changing the case of the keyword or function name, then try to build the program
again if you would like to experiment. Don't forget to save your source code file before
building.
Text that is printed to the screen between the quotation marks ("") can be changed as
this is just a text message and not part of the C language. Type any message between
the quotation marks that you would like and then save, compile and load the program to
the Arduino to run it.
The \r (carriage return) and \n (linefeed or newline) that forms part of the text between
quotation marks moves the cursor to the beginning of a new line in the terminal window.
It tells the compiler to insert a carriage return and newline character at the end of the
text message. To see what the carriage return and newline characters do, print a second
line of text after the first line as shown in the newline project code listing. Without the
carriage return and newline character at the end of the first line of text, the second line of
text would appear next to the first line of text and not below it.
Create a new project called newline following the same steps used to create the first
project in section 1.6.1 on page 27. Select the same working folder that you created
for your projects and the new project will be saved in its own folder inside the working
folder.
● 34
Chapter 1 • Your First C Program
Project name: newline
Project location: CH1\newline
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
UartInit();
printf("Hello, world!\r\n");
printf("Newline");
}
while(1) {
}
Program Output
Hello, world!
Newline
Try removing the \r\n from the first line of text, save, build and load the program to see
what it does. Now add some more \r\n's to the text, e.g. printf("Hello, world!\r\n\r\n\r\
n\r\n"); will move the cursor down four times in the terminal window so that when the
program is run, there will be a vertical gap between the first line of text and the second
line of text. You can also try putting a carriage return and newline character in front
of the text or in the middle of the text and see what happens, just make sure that it is
between the quotation marks.
Hint
Clearing the terminal window – To clear the terminal window between running different
programs, click Edit → Clear screen on the top Tera Term menu bar.
Re-running a program – To re-run a program without reloading it to the Arduino, press
the RESET button on the Arduino.
1.8 • Analysing the Program
We will now examine each element of the hello project code. Refer to Figure 1-10 on
page 37 when reading this section.
1.8.1 • Characters and Strings
A character is any keyboard character that you can type such as letters of the alphabet,
numbers and special characters like $, %, !, etc. A string is a sequence of characters –
written sequentially in a program and stored sequentially in the microcontroller's memory.
In C, strings are written between quotation marks as in "Hello, world!\r\n".
When you press the Enter key on the keyboard, you are typing a newline character.
The newline character moves the cursor to a new line as we saw in our program. If you
want to insert a newline character into a text string when editing your C source file, and
you try to do so by pressing the Enter key, the cursor in your text editor program will
● 35
C Programming with Arduino
move to the next line in the source file. This is because the text editor is interpreting the
newline character that you type. To insert a newline character into your text string for
your program to use, we use an "escape sequence" – this is the \n in the hello project
code. Windows systems actually inset a carriage return (\r escape sequence) and newline
character when pressing Enter in a text file. There are other escape sequences besides
the newline and carriage return, they all start with a backslash (\) when written in C
programs.
\r\n or \n
By convention, Windows systems use a carriage return and newline character at the end
of a line of text in a text file to indicate that the next line of text must be displayed on
a new line. These characters are represented by the escape sequences \r and \n in C
program text strings.
Unix systems use only a newline character in text files to indicate the end of a line of
text and that the next line of text is to appear on a new line – represented by \n in a C
program string.
These conventions usually occur in other programs such as terminal emulators, which is
the case with Tera Term that needs to be fed both \r and \n to move the cursor to the
beginning of a new line of text. It does not necessarily have to be the case that a terminal
emulator created for Windows follows the text file implementation and you may find a
terminal emulator for Windows that uses the single newline character to move the cursor
to the beginning of a new line in the terminal emulator window. In this case, the line of
code containing \r\n would not need the \r and could be changed to:
printf("Hello, world!\n");
1.8.2 • Functions
A function in C is a piece of code that can be called, or run, from within a C program
to perform some task (or function). In the text a function is written with the function
name first and then two parentheses following it, e.g. myfunc(). Functions are called
subroutines in some programming languages.
Every C program has to have a function called main(). This is the entry point of the C
program – where it will start running. The main() function has an opening and closing
brace ({ and }) around the code that it contains. This lets the compiler know that the
code within the braces belongs to the main() function. Ignore the int and void keywords
at the main() function for now.
The printf() function is an external function that is called by the main() function in the
hello program. What this means is that by typing the function name printf() in your C
source code, a function called printf() will be run when the hello program is run. The
printf() function exists somewhere externally and gets added to the program when the C
toolchain programs are run during the build process. All you need to know for now is that
the printf() function will print text to the terminal window when a line of text is passed
to it between quotation marks.
UartInit() is another function and is necessary to set up the serial port of the Arduino
so that calls to printf() will send their text out of the serial port for display in the
terminal window.
This is all that you need to know about functions for now, they will be covered in more
detail later in the book.
● 36
Chapter 1 • Your First C Program
Figure 1-10 Elements of a C Program
1.8.3 • Program Statements
The lines of code between the braces of the main() function are each examples of C
statements. Note that every statement in C must be terminated with a semicolon (;). If
the semicolon is left out, the compiler will display an error message and will not compile
the source code.
1.8.4 • Preprocessor Directives
Line 1 of the hello.c program contains a preprocessor directive: #include <stdio.h>.
This tells the compiler to include an external file named stdio.h. For now all you have to
know about this line is that it must be present at the top of your source code file if you
want to use the printf() function.
In the same way, the second line of the program contains a preprocessor directive
#include "stdio_setup.h" which is necessary when using the UartInit() function.
The reason why one include statement contains angle brackets and the other contains
quotation marks will be explained later.
1.8.5 • Whitespace Characters
The C compiler will ignore all whitespace characters in the source code. Whitespace
characters are spaces, tabs and the newline character. So you could separate each line
of the hello program vertically by hundreds of newline characters. Of course this would
make the program difficult to read, but the compiler would still compile the program.
● 37
C Programming with Arduino
1.8.6 • Ending the Program
A C program written for a PC would typically run, and when finished, simply exit and
return to the operating system. A C program running on an embedded system without
an operating system does not have anywhere to return to when it is finished, resulting
in the microcontroller fetching and executing the next program instruction from memory
whether it is valid or not. This can cause unknown and undesired behaviour of the
microcontroller or embedded system. To prevent the microcontroller from trying to run
invalid instructions from memory beyond the end of the C program, an empty while(1)
loop is placed at the end of the C program. Loops will be explained in more detail later in
the book.
1.9 • Exercises
The best way of learning to program is to write programs. The following exercises are
very simple, but be sure to complete them in order to reaffirm what you have learned
in this chapter. The solutions to the exercises follow immediately after, but don't look
at them until you have attempted to do the exercises yourself. I suggest that you make
a subdirectory in your programming directory called exercises in which to save your
programming exercises.
1.9.1 • Three Text Lines
Write a program that uses a single printf() function to write three lines of text to the
terminal emulator program on the PC. The output should look like this:
The C
language
is great!
1.9.2 • Find the Errors
The following program contains three errors, see if you can spot them. If you can't, then
create the program and try to compile it. The error messages from the compiler should
help you to find and correct the errors. Error messages appear in the pane at the bottom
of Atmel Studio. Double-clicking an error message will take you to the line of code in the
editor where the error was found which may indicate that the error actually occurred
before this line of code.
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
UartInit()
printf("I can use two printf()\r\n")
printf("statements in my program.")
}
● 38
while(1) {
}
Chapter 1 • Your First C Program
1.10 • Solutions
1.10.1 • Solution to 1.9.1
Project name: CH1_1_three_lines
Project location: Exercises\CH1_1_three_lines
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
UartInit();
printf("The C\r\nlanguage\r\nis great!\r\n");
}
while(1) {
}
1.10.2 • Solution to 1.9.2
The terminating semicolon has been left off the end of each of the statements in the
program.
Project name: CH1_2_errors
Project location: Exercises\CH1_2_errors
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
UartInit();
printf("I can use two printf()\r\n");
printf("statements in my program.");
}
while(1) {
}
1.11 • Summary
•
C programs are written with a text editor and saved in a text file that ends with
a .c file extension. This file is known as a source file and contains source code.
•
In Atmel Studio, a new project must be created before writing a C program for
a microcontroller. The project stores the C source file as well as various settings
for the project.
•
When the C compiler is run as part of the build process, it produces an
executable file that can be run on a microcontroller (The tools that run actually
compile and then link – more about this later).
● 39
C Programming with Arduino
•
C is case sensitive. Functions and keywords must be written in the correct case.
C uses lowercase for keywords and standard library function names (e.g. for
the printf() function). Non-standard library functions and functions you write
yourself are not restricted to lowercase only.
•
Every C program must start with a main() function. This is where the program
starts running.
•
The main() function and every other function that you write must have its code
placed between braces {}.
•
All function names end with parentheses ().
•
Each statement in C must be terminated with a semicolon.
•
Common typing errors when learning the C programming language are: leaving
off the semicolon at the end of a statement, leaving off one or both of the
enclosing braces of a function and leaving off the parentheses of a function.
Congratulations, you have come a long way in this chapter! We will move on to some
more programming in the next chapter and cover the basics of the C programming
language. If you do not understand everything in this chapter, do not worry, things will
become clearer as you progress through the book.
● 40
Chapter 2 • C Basics
Chapter 2 • C Basics
What you will do in this chapter:
•
Write some more simple C programs
•
Learn about input, output and variables
•
Do some simple mathematical calculations
•
Learn how to find and fix program errors
There are a number of example programs in this chapter and the chapters that follow. It
is advisable to enter and run all the example programs in this book. Feel free to change
the programs and experiment with them once you understand how they work. This will
help you to learn C.
2.1 • Input, Output and Variables
All programs work with input and output. The hello program worked with output only by
displaying a message in the terminal emulator window. The message was sent from the
Arduino making it output from the Arduino. If the terminal program is used to send a
character to the Arduino, this is know as input because it is sent into the Arduino.
Most programs will receive some input, do processing on the input and then produce an
output that is useful. An embedded system may read the temperature (input), decide
whether the temperature is too high or too low and then switch off or on a heating
element (output) as shown in Figure 2-1.
Figure 2-1 Example of Input and Output in an Embedded System
If we are to get an external measurement or value into the Arduino to do some
processing on, we need to store the value in the AVR microcontroller memory so that we
can then use it in our program. The value that is read is stored in a variable.
A variable is a piece of memory set aside to store a variable value as opposed to a fixed
or constant value. A temperature is an example of a variable value. When it is read by
the Arduino it is not known by the Arduino and could be any value within a range. If I say
that the Arduino reads an external temperature, what is happening is that the Arduino is
running a program and the program tells the Arduino to read a value. The act of reading
the value is the act of getting the value and storing it in memory as a variable.
● 41
C Programming with Arduino
To demonstrate input, output and variables on the Arduino in a useful way, we will get a
number from the user of the program (typed in by the user on the keyboard), perform
addition with the number and then output the result to the terminal program. Before we
can do this, we need to be able to:
1.
get a value that the user types in on the keyboard
2.
store the value on the Arduino so that we can add a number to it
3.
be able to display the result of the addition to the user in the terminal program.
Let’s start by looking at how to print a number to the terminal program. We’ll use the
printf() function again. Create a new C project from the template file called number
and add the following lines of code to the C file.
Project name: number
Project location: CH2\number
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
UartInit();
printf("Printing a number: 19\r\n");
printf("Printing a number: %d", 19);
}
while(1) {
}
Program Output
Printing a number: 19
Printing a number: 19
Save, build and load the program.
The program shows that you can print a number as part of the text as with the first
printf() function; or as an external number that gets substituted into the text. In the
second printf() function, the number 19 gets substituted in place of the %d format
specifier. We will look at format specifiers in more detail in later programs. We will need
the second method of printing a number that is variable (in this program the number is
a constant and we are substituting a constant into the string – it is hard-coded into the
program and can’t be changed after compiling).
The first way of printing the number will not allow a stored value entered by a user of the
program to be displayed on the screen because it is fixed as part of the text string (it is
known as a string constant). We will substitute a stored variable value into the string just
like the number 19 is substituted into the second text message.
The number program might not make much sense right now, as the two printf()
statements produce exactly the same output although they do so in a different way.
● 42
Chapter 2 • C Basics
2.1.1 • Variables
As already explained, a variables in C can store or hold a value that is input by the user.
We can also store the result of a calculation in a variable. If you were to add two numbers
together in a program, the result would need to be stored somewhere so that it can be
displayed to the user – it is stored in a variable. Variables can store different kinds of data
and each kind of data must be specified when declaring a variable as the next program
illustrates.
Create the following project called var.
Project name: var
Project location: CH2\var
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
int myvar;
UartInit();
myvar = 8;
printf("The value of variable myvar is: %d", myvar);
}
while(1) {
}
Program Output
The value of variable myvar is: 8
In this program, an integer variable is defined:
int myvar;
This tells the microcontroller that it must make space in memory for a variable of type
integer (int). The variable is given a name (myvar) so that we can identify it and use it in
our program.
An integer is a whole number (not a fraction) e.g. 5, 1, 94, 0, -3, 783, -102. The name
of the integer can be anything that you choose. It may not begin with a number (0 to 9),
may not contain any spaces (use the underscore character _ instead if you choose) and
may not contain any special characters like !, +, *, #, ? or dots as most of these have
special meaning in C. You may not use any of the C language keywords as a variable
name either as these words are reserved and have special meaning in C.
Or put another way, a variable name can consist of any alpha-numeric characters and
the underscore character, but may not start with a numeric character or consist of a C
keyword. It is also not advisable to start the variable name with the underscore character
as this is used by the compiler for internal variable names.
Syntax highlighting, as seen in the Atmel Studio editor, is helpful in identifying keywords
● 43
C Programming with Arduino
in C. If a word that you choose to use as a variable name changes to the same colour as
a keyword after typing it in, you will know that it is a keyword and not to use it.
The variable is assigned a value of eight in the program:
myvar = 8;
This means that myvar, now contains the value eight – it is a location in memory and
it is storing the value eight. The name of the variable is only used in the C source code.
When the program is compiled and run, the variable will be allocated a space in the AVR’s
memory and will be referenced by its address in memory.
In the printf() statement, the value of the variable myvar, i.e. 8, is substituted into
the text string in place of the format specifier %d by putting the name myvar after the
comma.
In the next program we will define a variable for storing the result of an addition
calculation. Create the following project called add.
Project name: add
Project location: CH2\add
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
int result;
UartInit();
result = 3 + 4;
printf("The result of 3 + 4 is: %d", result);
}
while(1) {
}
Program Output
The result of 3 + 4 is: 7
Here we see the addition operator (+) being used to add two numbers together. The
result of adding 3 and 4 is stored in an integer variable named result. The value that
this variable is holding is then printed to the terminal program window.
We now have the ability to store a value as a variable, perform addition and print the
variable to the screen. We need one last thing before we can write our input/output
program – the ability to get a number from the user of the program.
2.1.2 • Getting Input from the User Using the scanf() Function
The scanf() function is used to get input from the user. Like the printf() function, the
scanf() function uses format specifiers to specify what type of data it is using. We have
only seen one format specifier so far (%d) which is used with decimal integer numbers and
● 44
Chapter 2 • C Basics
is used again with scanf() in the next project.
To see the scanf() function in action, create the following project called input.
Project name: input
Project location: CH2\input
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
int input;
UartInit();
printf("Enter an integer number: ");
scanf("%d", &input);
printf("\r\n\r\nYou entered %d", input);
}
while(1) {
}
Example Program Output
Enter an integer number: 23
You entered 23
When you run this program, it will ask you for an integer number. Type an integer
number into the Tera Term terminal window a shown in Figure 2-2 on page 46. When
finished typing the number, press Enter. The program will then display the number that
you typed.
The scanf() function waits for the user to enter a number and then stores the number
in the variable input. After the number is stored in the variable, it can be printed to the
screen as done in the previous program.
scanf() requires that the address of the variable is passed to it, and this is done with the
address operator (&). Remember that when the variable is defined, it is allocated a space
in memory. The memory location has an address and by using the ampersand symbol (&)
in front of the variable name, we are supplying the scanf() function with the address of
the variable. This is a requirement of the scanf() function. If you used the variable name
without the ampersand, scanf() would then be getting the value that the variable holds
instead of the address of the variable. If you do not fully understand this, do not worry, it
will become clear later.
If you want to use the scanf() function in your programs, you need to have the line
#include <stdio.h> at the top of your source file – the same as for the printf() function.
● 45
C Programming with Arduino
Figure 2-2 Entering Data into Tera Term to Send to the Arduino
Although this program is called input, it actually uses input and output functions, but
does nothing to change the input value. In the next program we will achieve our earlier
objective of receiving input, processing it and then producing output. Create the next
project in Atmel Studio called inout.
Project name: inout
Project location: CH2\inout
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
int input, result;
UartInit();
printf("Enter a number to add 5 to: ");
scanf("%d", &input);
result = input + 5;
printf("\r\n%d + 5 = %d", input, result);
}
while(1) {
}
Example Program Output
Enter a number to add 5 to: 31
31 + 5 = 36
● 46
Chapter 2 • C Basics
Two integer variables are used in this program – input and result. They are both
defined on the same line and separated by a comma. They could also be defined on two
separate lines as follows:
int input;
int result;
The program asks the user to enter a number. The number obtained from the user by the
scanf() function is stored in the input variable, 5 is then added to the value stored in
input and the result of this calculation is stored in the variable called result.
The values contained in both input and result are passed to the printf() function.
Note that we have two format specifiers (%d). The variable input is written first in the
printf() function, so it gets substituted into the first format specifier. The variable
result is written second and gets substituted into the second format specifier as
illustrated in Figure 2-3.
Figure 2-3 Substitution of Variable Values into Format Specifiers
2.2 • Variable Types
So far we have used only integer variables, but there are other types of variables. The
next project called intdiv will illustrate the necessity for a different type of variable.
Project name: intdiv
Project location: CH2\intdiv
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
int result;
UartInit();
result = 3 / 4;
printf("3 divided by 4 equals %d", result);
}
while(1) {
}
Program Output
3 divided by 4 equals 0
This program displays the result of a division calculation using the division operator (/),
but the result is not as expected. We would expect the result of 3 ÷ 4 to be 0.75, but the
result shown is 0.
● 47
C Programming with Arduino
The reason for this result is that we are doing the division with integers and storing the
result of the calculation in an integer variable.
Integers are whole numbers and in the microcontroller’s memory can’t hold fractions
like the result of our calculation, so the fractional part of the result of the calculation is
discarded. If we divided 7 by 4, the result would be 1 instead of the expected 1.75, again
this is because the fraction is discarded. To overcome this problem, we use a different
variable type called a floating point variable.
2.2.1 • Floating Point Variables
Floating point variables are able to store fractional numbers as the next project, fltdiv,
shows.
Project name: fltdiv
Project location: CH2\fltdiv
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
float result;
UartInit();
result = 3.0 / 4.0;
printf("3.0 divided by 4.0 equals %f", result);
}
while(1) {
}
Program Output
3.0 divided by 4.0 equals 0.750000
A variable of type float (floating point variable) named result is defined in the
program. This variable is able to hold a floating point number. When we want to specify a
constant floating point number in C it must contain a decimal point such as 3.0 and 4.0 in
this program.
To print out the floating point result using printf(), the format specifier for floating point
numbers is used - %f, as can be seen in the program.
2.2.2 • Character Variables
Character variables are used to store individual characters as the next project charvar
shows.
● 48
Chapter 2 • C Basics
Project name: charvar
Project location: CH2\charvar
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
char ch;
UartInit();
printf("Type a character: ");
scanf("%c", &ch);
printf("\r\nYou typed %c", ch);
}
while(1) {
}
Example Program Output
Type a character: b
You typed b
A character variable named ch is defined in order to store a character typed in by the
user. The scanf() and printf() functions both use the %c format specifier in order to
work with the character data type.
To initialise a character variable with a value in a C program, the character is written
between single quotes (apostrophes). For example:
char ch;
ch = 't';
The first line defines a character variable with the name ch. The character t is then stored
in the variable ch.
There is more to be said about variables, but we will use our knowledge gained so far to
write some useful programs that do mathematical calculations.
2.3 • Arithmetic Operators
Ohm’s law can be used to calculate the current flowing through a resistor if the voltage
across the resistor and resistance of the resistor are known. Figure 2-4 on page 50
shows the circuit used to demonstrate this. The resistor could represent a light bulb or
relay coil for example. If you were to add an LED to your embedded system, you would
need to be able to use Ohm’s law to calculate the value of the current limiting resistor
needed by the LED.
To calculate the current flowing through the resistor, simply divide the voltage across the
resistor by the resistance of the resistor. The formula is represented mathematically by
the following equation:
● 49
C Programming with Arduino
I=V÷R
Where: I is current in Amps
V is voltage in Volts
R is resistance in Ohms
For example, if the voltage of the battery is 10V and the resistance of the resistor is 3Ω
(3 ohms), then the current flowing in the circuit is 10 ÷ 3 = 3.333 Amps.
Figure 2-4 Circuit for Demonstrating Ohm’s Law
The next program called ohm.c asks the user for the voltage and resistance and then
calculates the current using the above equation.
Project name: ohm
Project location: CH2\ohm
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
float V, R, I;
UartInit();
printf("Current calculation using Ohm's Law.\r\n");
printf("\r\nEnter the voltage in Volts: ");
scanf("%f", &V);
printf("\r\nEnter the resistance in Ohms: ");
scanf("%f", &R);
I = V / R;
printf("\r\n\r\nCurrent in Amps is %f A", I);
}
● 50
while(1) {
}
Chapter 2 • C Basics
Example Program Output
Current calculation using Ohm's Law
Enter the voltage in Volts: 5
Enter the resistance in Ohms: 2
Current in Amps is 2.500000 A
The equals sign (=) is known as the assignment operator. The variable to the left of the
assignment operator is assigned the value of whatever is to the right of it. In the above
example, the variable I is assigned the result of the division of two variable values.
You have already seen the + (addition) and / (division) arithmetic operators. Table 2-1
shows all of the arithmetic operators used in C.
Table 2-1 Arithmetic Operators
Operator
Mathematical
Symbol
+
-
Meaning
Example
Result
+
Addition
10 + 34
44
-
Subtraction
16 - 7
9
*
×
Multiplication
4*3
12
/
÷
Division
18.5 / 10.0
1.85
%
rem.
Remainder
(modulo)
17 % 5
2
The left column shows each operator as it is typed into the C source file using the
keyboard. Everything should be straight forward except for the modulo operator (%). The
modulo operator gives the remainder left over from a division operation. In the example
in the table, 5 will divide into 17 three times leaving a remainder of 2.
2.4 • Field Width Specifiers
The output of the fltdiv program and the ohm program produce floating point numbers
with 6 digits following the decimal point which is unnecessary. It is possible to change
the number of digits displayed when using the printf() function by using a field width
specifier as the next program, fwidth.c, shows.
As can be seen in the program, by inserting .2 between the % and the f in the format
specifier, the number of digits displayed after the decimal point is reduced to two, and
inserting .3 reduces the number of digits to three.
● 51
C Programming with Arduino
Project name: fwidth
Project location: CH2\fwidth
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
UartInit();
printf("Float with 2 trailing digits: %.2f", 43.140000);
printf("\r\nFloat with 3 trailing digits: %.3f", 43.140000);
}
while(1) {
}
Program Output
Float with 2 trailing digits: 43.14
Float with 3 trailing digits: 43.140
2.5 • Compiling and Linking
When using Atmel Studio to create an executable file that can be loaded to the Arduino,
there are two main steps involved. The first is called compiling and the second is called
linking. Atmel Studio uses the GCC toolchain for AVR microcontrollers to compile and link
a project when the build button is clicked to build the project. It is important to know this
because programming errors can come from the compile step and the linking step. Being
aware of this will help to find the source of the errors. Figure 2-5 on page 53 shows a
diagram of the compile and link process.
The compiler compiles the main.c file and the stdio.h file to produce an object file called
main.o. The compiler knows that it must look for the stdio.h file because of the line at the
top of the hello.c source file - #include <stdio.h>. This line is simply telling the tools
to include this file in the compile step. The file stdio.h comes with the compiler and was
installed when you installed Atmel Studio.
Figure 2-5 on page 53 has been simplified to show only the compiling and linking
stages and also excludes the stdio_setup.h file. In reality there is also a preprocessor
stage to the build process. All the projects that have been built so far have functions for
setting up the serial port of the Arduino for using the printf() and scanf() functions
found in an additional C source file that is not shown in the figure.
The printf() function that you have been using exists externally as mentioned before. It
actually exists in what is known as a library file. In the figure, library files and the object
file are linked together.
Linking is the process of adding the external functions used in the program to the
executable file. The linker "resolves externals" – that means that it sees, for example,
that the external function printf() was used in the program, it then looks for this
function in object or library files and adds it when found. If it can’t find the function, it will
give an "unresolved externals" error and will not be able to produce the executable file.
The linker does not link the entire library file with the object file. It only links the
functions from the library file that are used in the program. The library files were installed
● 52
Chapter 2 • C Basics
with the C toolchain when Atmel Studio was installed.
The linker produces an intermediate file in ELF format which is then converted to a HEX
file (a file in hexadecimal format) using a utility program that is part of the toolchain. The
HEX file is the executable file that Atmel Studio uses in conjunction with a programming
device (AVRISP or Dragon) to load the compiled and linked program to the target
embedded system.
Figure 2-5 Compile and Link Process
2.6 • Errors and Warnings
Errors will prevent the compiler from compiling and producing an object file if the error is
a compile error. If an error occurs when linking, the linker will not be able to produce an
executable file.
Warnings will alert the programmer that something may be wrong in the program. The
program will still compile and link, but may not work correctly. The compiler can be set
up to treat warnings as errors and not produce any output if any warnings occur. In Atmel
Studio this setting and other compiler and linker settings can be found by using the top
menu and selecting Project → <project name> Settings... which opens the properties
page for the currently open project. Clicking the Toolchain tab on the left of the properties
page will allow various toolchain settings to be changed as shown in Figure 2-6 on page
54. Figure 2-6 on page 54 shows where to set the compiler to treat warnings as
● 53
C Programming with Arduino
errors if desired.
2.6.1 • Compile Errors
Errors that occur during compiling are usually syntax errors such as a keyword being
spelled wrong or a terminating semicolon that is left off. Another example that will
produce a compile error is if two variables are defined that have the same name. In C
each variable defined must have a unique name.
Figure 2-6 Toolchain Settings Include Settings for the Compiler and Linker
As an exercise open the ohm project that you created previously, and we will purposely
put some errors in the code to see what the compiler and linker will do.
Remove the semicolon from the end of the variable definition to invoke the first error. The
variable definition should now look as follows.
float V, R, I
Save the main.c file and then build the project. The compiler errors that are displayed are
shown in Figure 2-7 on page 55.
● 54
Chapter 2 • C Basics
Figure 2-7 Errors Generated in the ohm Project
The first error shows that something was expected before the function UartInit() and
the error message includes some suggestions on what could be missing. Double-clicking
the error message will move the cursor to the line in the code where the error was first
discovered. Because the compiler ignores whitespace characters, it does not matter
where the terminating semicolon for the variable definition occurs. The compiler only
knows that the terminating semicolon is missing when it reaches the function call in the
next statement. This is the reason why the error is marked as occurring on a line of code
that has no errors.
The missing semicolon also generates a second error because the variable I now appears
not to exist. The error message shows that the error was first discovered on line 15,
several lines of code down from where the actual error exists. The reason for this is
that the variable I is used on line 15 for the first time in the code and this is where the
compiler discovered that it is missing.
From the above exercise we can see that a single error can produce more than one error
message and that the line number that the error message reports is not necessarily the
line that the error is actually on. Fix the error by replacing the semicolon at the end of the
variable definition.
Change the name of variable R in line 14 of the code to Res as shown below. Save the file
and build the project.
scanf("%f", &Res);
This time the error message shows ‘Res’ undeclared (first use in this function) and that
it occurs on line 14. The compiler does not recognise the identifier Res because it has not
been defined to be a variable or anything else in the program and has not been declared
to be an external identifier. In this case the line number that the error occurred at is the
same line number that the compiler error message reports. Fix the error, we will look at
link errors next.
● 55
C Programming with Arduino
2.6.2 • Link Errors
To produce a link error, change the following line:
printf("Current calculation using Ohm's Law.\n");
to
prinf("Current calculation using Ohm's Law.\n");
i.e. spell printf incorrectly. Save the file and build the project.
This will produce a linker error because, by spelling the name of the function incorrectly,
we are referring to a function that does not exist. The compiler will actually warn you first
that there is a function referred to that it does not know about. Because the linker looks
for the unknown function in order to link it into the program, it will produce the error
message, rather than the compiler. The linker will try to find the function in one of the
library files, but will not find it.
The warning message is from the compiler, but it still compiles correctly and produces
an object file. The error message is from the linker and it fails to complete the linking
process because of this error. The linker displays the error message undefined reference
to ‘prinf’ indicating that it does not know what "prinf" is.
A second error message says that ld (the name of the linker program) returned 1. If a
program returns 0, to the operating system or program that ran it, it means that the
program succeeded in what it was asked to do. If the program returns anything else it
means that the program failed. In this case the linker program returned 1, indicating that
it failed to do the linking that it was asked to do.
If you can’t tell if the error is from the compiler or linker, try compiling the C source file
on its own. If the compile does not produce any errors, then the error must be from
the linker. To compile a file without building the whole project, first clean the project by
selecting Build → Clean Solution or Build → Clean <project name> from the top menu.
This will delete any object files from the project and ensure that the file that you compile
will actually be compiled again. Right-click the name of the file in the Solution Explorer
pane at the right of Atmel Studio and then click Compile on the pop-up menu.
2.7 • Exercises
2.7.1 • Ohm’s Law Calculations
Write two program that use Ohm’s law to calculate:
a.
The resistance of a resistor if the voltage across the resistor and the current
flowing through the resistor is known.
b.
The voltage across the resistor if the current flowing through the resistor and
the resistance of the resistor is known.
The program must ask the user to enter the two known quantities each time and then
calculate the unknown quantity. The result must be displayed with two digits after the
decimal point, use floating point variables throughout.
Use the following mathematical formulas to calculate the resistance and voltage. They
have been derived by manipulating the formula that you saw earlier in the chapter:
● 56
Chapter 2 • C Basics
To calculate resistance: R = V ÷ I
To calculate voltage: V = I × R
2.7.2 • Variable Names
Which of the following are valid variable names?
number8
8th_number
particle
void
big number
small_number
If you don’t know the answer, try using them in a program and see what the compiler
says.
2.8 • Solutions
2.8.1 • Solution to 2.7.1
a.
Calculate resistance
Project name: CH2_1a_resistance
Project location: Exercises\CH2_1a_resistance
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
float R, V, I;
UartInit();
printf("Calculate resistor value:\r\n");
printf("Enter voltage: ");
scanf("%f", &V);
printf("\r\nEnter current: ");
scanf("%f", &I);
R = V / I;
printf("\r\nResistance is: %.2f Ohms", R);
}
while(1) {
}
● 57
C Programming with Arduino
b.
Calculate Voltage
Project name: CH2_1b_voltage
Project location: Exercises\CH2_1b_voltage
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
float R, V, I;
UartInit();
printf("Calculate voltage:\r\n");
printf("Enter current: ");
scanf("%f", &I);
printf("\r\nEnter resistance: ");
scanf("%f", &R);
V = I * R;
printf("\r\nVoltage is : %.2f volts", V);
}
while(1) {
}
2.8.2 • Solution to 2.7.2
Valid variable names:
number8
particle
small_number
Invalid variable names:
void – it is a C keyword
big number – spaces are not allowed in variable names
8th_number – variable names may not start with a number
2.9 • Summary
● 58
•
C program statements are run in order from top to bottom.
•
Variables are used to store data in a program. The value that a variable is
holding can be changed when the program is running.
•
When defining a variable in C, the type of data that the variable will hold must
be specified. (e.g. int, float, char)
•
Constants are numbers that are "hard coded" into a program and can’t be
changed after compiling.
•
A variable can be assigned the value of a constant, but the value of the variable
can be changed in a running program. e.g.:
Chapter 2 • C Basics
int var;
var = 25;
25 is a constant.
var is a variable.
var can be assigned a different value later in the program.
•
Most programs will get input, perform a process on the input and produce an
output.
•
Arithmetic operators are used to perform basic mathematics on variables and
constants.
•
C source code is first compiled to produce an object file. The object file is then
linked with functions from a library file in order to produce a working program.
•
Errors and warnings from the C tools can occur during the compile and link
stages.
This chapter has taken you through some of the basics of the C language. Almost every
topic can be expanded on, but has been kept brief for now to increase your knowledge of
C, but not overwhelm you with extra details that are unnecessary at this stage.
In the next chapter we will look at how a C program can be written to make decisions.
● 59
C Programming with Arduino
● 60
Chapter 3 • Comparative Operators and Decisions
Chapter 3 • Comparative Operators and Decisions
What you will do in this chapter:
•
Learn how to compare integer values
•
Write programs that make decisions
3.1 • Comparative Operators
Comparative operators are used to compare values, for example – is the value of variable
X bigger than the value of variable Y?. These values can be stored as constants or
variables. Comparative operators are also known as relational operators.
3.1.1 • True and False
When an expression containing a comparative operator is evaluated, it will evaluate to
either true or false. Logic systems and computers represent true by the value 1 and false
by the value 0. The next example program called compare will show what this means.
Project name: compare
Project location: CH3\compare
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
int temper = 20;
UartInit();
printf("Is the temperature less than 23? %d\r\n", temper < 23);
printf("Is the temperature more than 23? %d\r\n", temper > 23);
printf("\nTemperature is now increasing...\r\n\r\n");
temper = 50;
printf("Is the temperature less than 23? %d\r\n", temper < 23);
printf("Is the temperature more than 23? %d\r\n", temper > 23);
}
while(1) {
}
Program Output
Is the temperature less than 23? 1
Is the temperature more than 23? 0
Temperature is now increasing...
Is the temperature less than 23? 0
Is the temperature more than 23? 1
● 61
C Programming with Arduino
In this example, a variable called temper is defined in order to store a temperature. The
initial value assigned to the variable is 20, representing 20 degrees Celsius. As can be
seen in the example, a variable can be defined and initialised with a value in one line;
the examples in the previous chapter used two lines to do this. In the first printf()
statement, the value stored in temper is compared to the constant value 23 using the
mathematical "less than" sign: temper < 23. In other words we are saying "Is the value
stored in the variable temper less than 23?"
When this expression is evaluated in C, it will evaluate to either true (1) or false (0).
The result is then substituted in place of the format specifier. As can be seen when
the program is run, the result of asking whether 20 (stored in temper) is less than 23
evaluates to 1 (true). Logically this is because 20 is less than 23. See Figure 3-1.
Figure 3-1 A Comparative Operator in Action
In the second printf() statement, we ask whether 20 is greater than (>) 23 and get a
result of 0 (false).
When the value that temper is holding is changed to 50, and we ask the same questions,
we get a result of false for our question "Is 50 less than 23?" and true for "Is 50 greater
than 23?" as we would expect.
3.1.2 • C Comparative Operators
Table 3-1 shows the comparative operators available in C. You have already seen the less
than and greater than operators in use.
Table 3-1 C Comparative Operators
Operator
Meaning
Example
Result (where 1 = true and 0 = false)
<
Less than
A<B
1 if A is less than B , else 0
>
Greater than
A>B
1 if A is greater than B , else 0
<=
Less than or equal to
A <= B
1 if A is less than or equal to B , else 0
>=
Greater than or equal to A >= B
1 if A is greater than or equal to B , else 0
==
Equal to
A == B
1 if A is equal to B , else 0
!=
Not equal to
A != B
1 if A is not equal to B , else 0
3.2 • Decisions
Comparative operators are used to make decisions in a C program. So far you have seen
programs that execute or run each statement in the program line for line from top to
bottom. Decisions can be used to jump into or over a particular part of a program.
Getting back to our temperature controller example from Chapter 2 (Figure 2-1 on
page 41), a decision must be made to switch the element of the system on or off. The
● 62
Chapter 3 • Comparative Operators and Decisions
system needs to know if the temperature is too low, and if it is, switch the heater element
on. When the temperature is too high, it must make a decision to switch the element off.
3.2.1 • Using if to Make a Decision
The if keyword is used in a statement to make a decision as the next program whatif
shows.
Project name: whatif
Project location: CH3\whatif
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
int temper;
UartInit();
printf("Enter a temperature (0 to 100): ");
scanf("%d", &temper);
if (temper >= 30) {
printf("\r\n\r\nSwitch the heater OFF");
}
}
while(1) {
}
Example Program Output
Enter a temperature (0 to 100): 47
Switch the heater OFF
This program asks the user to enter a temperature between 0 and 100. The if statement
is used to determine if the temperature that was entered is greater than or equal to 30. If
it is, a message is printed. If not the program skips the block of code between the braces
under the if statement.
In an if statement, the expression between the parentheses must evaluate to true (1) in
order for the block of code between the braces to be run. If the expression evaluates to
false (0), the entire block is skipped.
When you run this program and enter a value that is below 30, it will appear that the
program does nothing. This is because there is no other code that prints any output after
the if statement is evaluated. Try modifying the program by adding a line above while(1)
that prints a message such as: "End of program.".
3.2.2 • Using else in Conjunction with if
The problem with using the whatif program for controlling a temperature is that it will
not do anything to switch the heater on if the temperature drops below 30. The else
statement is used to run a block of code when the if statement evaluates to false. The
● 63
C Programming with Arduino
ifelse program demonstrates how this is done.
Project name: ifelse
Project location: CH3\ifelse
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
int temper;
UartInit();
printf("Enter a temperature (0 to 100): ");
scanf("%d", &temper);
if (temper >= 30) {
printf("\r\n\r\nSwitch the heater OFF");
}
else {
printf("\r\n\r\nSwitch the heater ON");
}
}
while(1) {
}
Example Program Output 1
Enter a temperature (0 to 100): 38
Switch the heater OFF
Example Program Output 2
Enter a temperature (0 to 100): 19
Switch the heater ON
Now if the temperature is greater than or equal to 30, a message is displayed to switch
the heater off, otherwise a message to switch the heater on is displayed.
This program works the same as the previous one, except that should the if statement
evaluate to false, the code in the else block will run, skipping the code in the if block.
This is known as branching as the program will branch to the else statement. Should the
if statement be true, the code in the block below the if statement will be run and the
block of code under the else statement will be skipped over.
Figure 3-2 on page 65 shows the program flow of the ifelse.c program.
● 64
Chapter 3 • Comparative Operators and Decisions
Figure 3-2 Program Flow in an if – else Construct
In an embedded system that has the hardware in place to measure temperature and
switch a heater element, the on and off messages can be replaced with the code that
will actually switch the heater element on or off. The temperature of the quantity being
heated would be obtained by reading a temperature sensor attached to the embedded
system. The value obtained from the thermometer would be put into the temper variable.
In the body or block of both the if and else statements, there can be multiple
statements. e.g.:
if (x < y) {
statement1;
statement2;
}
else {
statement3;
statement4;
}
When the if evaluates to true, statement 1 and statement 2 will run. When the if
evaluates to false, statements 3 and 4 will run.
If the if or else blocks contain only one statement each, then the body of the if or else
is not required to have braces. The if – else construct used in ifelse could have been
written like this:
if (temper >= 30)
printf("\r\nSwitch the heater OFF");
else
printf("\r\nSwitch the heater ON");
I find that a program is easier to read when the enclosing braces of a single line
statement are in place, so I prefer to always put them in. This is a matter of personal
preference and programming style. Code examples in this book will contain a mix of both
methods when dealing with single line statements in the body of if and else blocks.
3.2.3 • The = Operator and the == Operator
It is important to know the difference between the assignment operator (=) and the
comparative operator (==) (also known as the equality operator).
The assignment operator copies the value or result of an expression that is to the right of
it into the variable to the left of it. e.g. var = 3;
The comparative operator compares the values to the left and right of it, and if they are
● 65
C Programming with Arduino
equal to each other, evaluates to true (1). If the values are not equal to each other, the
comparison evaluates to false (0). e.g. if (var == 3)
In an if statement, any non-zero value will evaluate to true (1). For example:
if (24) {
printf("This code will always run!");
}
When this if statement is evaluated, it will always evaluate to true because the number
between the parentheses is non-zero.
It is also possible to use the assignment operator within the parentheses of the if
statement as follows:
if (var = 3) {
printf("This code will always run!");
}
The variable var is assigned the value of 3 – var is therefore non-zero and will evaluate
to true, resulting in the code within the if statement block always being run. This
example code will compile and run and is perfectly legal in C, but may not be what
the programmer intended. Accidentally using the assignment operator instead of the
comparative operator is a common error, where the programmer intends to compare the
two values to see if they are equal, but ends up assigning a value to a variable instead.
The comparison of two constants by accidentally using the = assignment operator
will cause the compiler to generate an error. If the value to the left of the = sign is a
constant, the compiler will always generate an error.
Not possible to assign a constant to a constant
if (3 = 4) {
}
Legal to compare two constants (although the result of the comparison will always
be the same)
if (3 == 4) {
}
Not possible to assign a variable to a constant
if (3 = var) {
}
Legal to compare a constant with a variable
if (3 == var) {
}
Legal to assign a constant to a variable
if (var = 3){
}
Legal to compare a variable with a constant
if (var == 3) {
}
The next program, fussy, shows how the equality operator works.
● 66
Chapter 3 • Comparative Operators and Decisions
Project name: fussy
Project location: CH3\fussy
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
int temper;
UartInit();
printf("Enter a temperature (0 to 100): ");
scanf("%d", &temper);
if (temper == 30) {
printf("\r\n\r\nThe temperature is just right!");
}
else {
printf("\r\n\r\nThe temperature is too hot or too cold.");
}
}
while(1) {
}
Example Program Output 1
Enter a temperature (0 to 100): 78
The temperature is too hot or too cold.
Example Program Output 2
Enter a temperature (0 to 100): 30
The temperature is just right!
When run, the program asks the user for a temperature between 0 and 100. The ==
operator is used to check for an exact temperature of 30. Only if the user enters 30 will
the block of code under the if statement be run. If any other value is entered, the code
in the else block is run.
3.2.4 • Using else – if in Conjunction with if
If you want to write a program that checks for one of several possible values, you could
use multiple if statements to do the checking as the multif program shows.
In this program the user must enter either 1 or 2 to start a pump or mixer (represented
by displaying a message in the terminal window). If the user enters 1, the "pump started"
message will be displayed. The program will then check to see if the user entered 2,
which is unnecessary because the value of input in this case is 1. If the variable input
has the value of 1, it can’t have the value of 2 as well. If we wanted to add an else
statement to take care of an incorrect number entered by the user, this would be difficult
to check for. Thankfully it is possible to use the else if construct as shown in Figure 3-3
on page 68.
● 67
C Programming with Arduino
Project name: multif
Project location: CH3\multif
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
int input;
UartInit();
printf("Enter 1 to start pump\r\n");
printf("Enter 2 to start mixer\r\n");
scanf("%d", &input);
if (input == 1) {
printf("\r\nPump started.\r\n");
}
if (input == 2) {
printf("\r\nMixer started.\r\n");
}
}
while(1) {
}
Example Program Output 1
Enter 1 to start pump
Enter 2 to start mixer
1
Pump started.
Example Program Output 2
Enter 1 to start pump
Enter 2 to start mixer
2
Mixer started.
Figure 3-3 The else if Construct
In the else if construct, the comparison at the else if statement will be skipped,
should the result of the first if statement be true. Should the first if statement be false,
the comparison at the else if statement will be evaluated.
● 68
Chapter 3 • Comparative Operators and Decisions
Multiple else if statements can be placed after an initial if statement. A single else
statement can be used at the end to run code if all of the previous evaluations were false.
The pumpctrl program clarifies this.
Project name: pumpctrl
Project location: CH3\pumpctrl
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
int input;
UartInit();
printf("Enter 1 to start pump\r\n");
printf("Enter 2 to start mixer\r\n");
printf("Enter 3 to stop pump\r\n");
printf("Enter 4 to stop mixer\r\n");
scanf("%d", &input);
if (input == 1) {
printf("\r\nPump started.\r\n");
}
else if (input == 2) {
printf("\r\nMixer started.\r\n");
}
else if (input == 3) {
printf("\r\nPump stopped.\r\n");
}
else if (input == 4) {
printf("\r\nMixer stopped.\r\n");
}
else {
printf("\r\nInvalid option\r\n");
}
}
while(1) {
}
In this program, the value of the variable input is checked for four different values. If
none of the values are found, the code in the else block is run. If one of the values is
found, the code in the block under the statement that evaluates to true will be run. After
that, the program will continue to run at while(1) – all other statements in the else –
if construct are skipped.
Don’t forget that you can run the program multiple times by pressing the reset button on
the Arduino.
Example Program Output 1
Enter 1 to start pump
Enter 2 to start mixer
Enter 3 to stop pump
Enter 4 to stop mixer
1
Pump started.
● 69
C Programming with Arduino
Example Program Output 2
Enter 1 to start pump
Enter 2 to start mixer
Enter 3 to stop pump
Enter 4 to stop mixer
3
Pump stopped.
Example Program Output 3
Enter 1 to start pump
Enter 2 to start mixer
Enter 3 to stop pump
Enter 4 to stop mixer
9
Invalid option
True, 1, High and False, 0, Low
True, 1, High – In programming and logic systems true, 1 and high mean the same
thing. True has the logic value of 1 and is often referred to as high or logic high because it
is represented by the high voltage level in a logic system such as 5V.
False, 0, Low – The opposite of true, 1 and high is false, 0 and low. False, 0 and low all
refer to the same logic state with 0 being the logic value of false. Low or logic low refers
to the low voltage, or 0V (zero volts) of a logic system.
These three ways of referring to each of the logic states are often used interchangeably
when talking about logic systems and programming.
3.3 • Exercises
3.3.1 • Variable Compare
Write a program that compares two integer numbers of different values entered by the
user and then tells the user which one is bigger. Hint: We have been comparing variable
values with constants, but two variable values can also be compared.
3.3.2 • Variable Compare 2
Modify the previous exercise to test if the first number entered by the user is greater
than, less than or equal to the second number entered by the user.
3.4 • Solutions
3.4.1 • Solution to 3.3.1
This solution does not take into consideration the case where the two numbers are equal,
resulting in the code in the else block being run when the numbers are equal. The next
solution shows how to fix this problem.
● 70
Chapter 3 • Comparative Operators and Decisions
Project name: CH3_1_compare
Project location: Exercises\CH3_1_compare
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
int num1, num2;
UartInit();
printf("Enter a number: ");
scanf("%d", &num1);
printf("\r\nEnter a different number: ");
scanf("%d", &num2);
if (num1 > num2) {
printf("\r\n%d is bigger than %d", num1, num2);
}
else {
printf("\r\n%d is bigger than %d", num2, num1);
}
}
while(1) {
}
3.4.2 • Solution to 3.3.2
Project name: CH3_2_compare2
Project location: Exercises\CH3_2_compare2
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
int num1, num2;
UartInit();
printf("Enter a number: ");
scanf("%d", &num1);
printf("\r\nEnter another number: ");
scanf("%d", &num2);
if (num1 > num2) {
printf("\r\n%d is bigger than %d", num1, num2);
}
else if (num1 < num2) {
printf("\r\n%d is smaller than %d", num1, num2);
}
else {
printf("\r\n%d is equal to %d", num1, num2);
}
● 71
C Programming with Arduino
}
while(1) {
}
You could also check for num1 == num2 by using an else if block in place of the else
block above and leave the else block off altogether. This is, however, unnecessary as if
a number is neither greater than nor less than another number, it must be equal to the
other number. Alternatively you could firstly check if the two numbers are equal to each
other, then if the first is greater than the second. The else block would then be run if the
first number was less than the second. (Do any two of the checks in the if and else if
statements and the else statement will take care of the third condition).
3.5 • Summary
•
When two values are compared by a comparative operator in C, they will
evaluate to either true or false.
•
In a computer or microcontroller, true is represented by 1 and false is
represented by 0.
•
Decisions can be made in C by using the if statement to evaluate a
comparative expression.
•
When the comparative expression inside an if statement evaluates to true,
the block of code between braces under the if statement is run. Should the
expression evaluate to false, the block of code under the if statement will be
skipped over. The block of code is delimited by braces {}.
•
The else statement can be used to run a block of code when the if statement
evaluates to false. Should the if statement evaluate to true, the block of code
under the else statement will be skipped over (also known as branching or
jumping – this also occurs when a single if statement is false; program flow
branches over the if block).
•
Any non-zero value between the parentheses of an if statement will evaluate
to true.
•
A common error in C is to accidentally use the = assignment operator instead
of the == comparative operator when comparing two values to see if they are
equal.
•
if and else are both C keywords.
•
The else if construct can be used to test for one of several conditions or
values.
There are more ways to make decisions in C programs than shown in this chapter. We will
look at these in more detail in later chapters.
● 72
Chapter 4 • The while Loop and Commenting Code
Chapter 4 • The while Loop and Commenting Code
What you will do in this chapter:
•
Learn how to use the while loop
•
Improve the temperature controller example
•
Learn about commenting a program and programming style
In the previous chapter we saw that a program can be made to branch when making a
decision. Branching causes the regular flow of a program, from top to bottom through a
list of statements, to change. Loops also change the flow of a program as explained in
this chapter.
4.1 • The while Loop
The while loop will cause a block of program code to run repeatedly from top to bottom
while a certain condition is true. Running the code repeatedly is called looping.
The water program demonstrates the while loop.
Project name: water
Project location: CH4\water
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
int water_level = 0;
UartInit();
printf("Pumping 10L of water...\r\n\r\n");
while (water_level < 10) {
water_level = water_level + 1;
printf("Progress: %d\r\n", water_level);
}
printf("\r\nFinished.");
}
while(1) {
}
Program Output
Pumping 10L of water...
Progress:
Progress:
Progress:
Progress:
1
2
3
4
● 73
C Programming with Arduino
Progress:
Progress:
Progress:
Progress:
Progress:
Progress:
5
6
7
8
9
10
Finished.
The while loop evaluates a comparative expression to see if it is true or false in the same
way as the if statement did in the previous chapter. The block of code between the
braces under the while loop will run continually until the condition that it is evaluating
becomes false.
When the loop is run for the first time, the expression water_level < 10 is evaluated.
Because water_level was initialised to the value of 0, the result of the evaluation is
true (1). The code under the loop will now run because of this. If the evaluation had been
false, the program flow would have ignored or "jumped over" the entire block of code
inside the loop and carried on running any statements that occurred after this.
The first line of code that runs in the loop adds 1 to the value of the variable water_level
and then stores this result back in the water_level variable (0 + 1 = 1). This result is
printed to the terminal window.
Program flow has now reached the bottom of the loop, so the water_level < 10
expression is evaluated again. The value of water_level is 1, and 1 is less than 10, so
the evaluation result is true again and the first statement within the loop block is run
again.
Running of this first statement again results in the water_level variable now containing
the value 2 (1 + 1 = 2). As before, the result is printed to the terminal window and the
loop expression is evaluated again.
This process will continue looping and will increment water_level by one each time
through the loop and print the result to the terminal window.
Figure 4-1 The while Loop
When the value of water_level reaches 9, the loop expression will still evaluate to true.
After the incrementing of this variable on the next pass through the loop, it will be 10.
The value of 10 is printed out to the terminal window. The loop expression gets evaluated
again and evaluates to false because 10 is not less than 10. The code in the loop will now
not be run again. Program flow continues at the printf() statement under the body
of the while loop. Figure 4-1 shows how the while loop works. Upon running the two
statements in this loop, the expression will be evaluated again.
● 74
Chapter 4 • The while Loop and Commenting Code
4.2 • Using if Inside the while Loop
A while loop can be used to continually monitor a variable value as the following
program, monit, shows.
Project name: monit
Project location: CH4\monit
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
char key = 0;
int num = 50;
UartInit();
printf("Press u to increment number\r\n");
printf("Press d to decrement number\r\n");
printf("Press x to quit\r\n\r\n");
while (key != 'x') {
printf("Number is %d\r\n", num);
scanf("%c", &key);
printf("\r\n");
if (key
num
}
else if
num
}
== 'u') {
= num + 1;
(key == 'd') {
= num - 1;
}
printf("End of program.");
}
while(1) {
}
Example Program Output
Press u to increment number
Press d to decrement number
Press x to quit
Number is 50
u
Number is 51
u
Number is 52
u
Number is 53
d
Number is 52
d
Number is 51
x
End of program.
● 75
C Programming with Arduino
When this program is run, the user can press the ‘u’ key on the keyboard to add 1 to
a number (increment the number by one) or the ‘d’ key to subtract 1 from a number
(decrement the number by one). The value of the number is initially set to 50 and the
new value of the number is displayed on the screen each time that it is changed. The ‘x’
key can be pressed to quit the program which just breaks program flow out of the first
while loop, runs the printf() statement below it and then drops into the continuous
while(1) loop. Note that the program will not work if your Caps Lock key is on because
the program is looking for lowercase characters only.
In this program, two variables are defined and initialised with values – one is used to
store the character of the key pressed by the user on the keyboard, the other is used to
store the count value used in the program. The three printf() statements at the top of
the program simply give the user instructions on how to use the program.
The code in the first while loop will keep running so long as the variable key does not
contain the character ‘x’. The character variable key is initialised to the value 0 at the
start of the program to ensure that it does not contain ‘x’, which could happen if this
variable is not initialised. An uninitialised variable can contain any random value that
happens to be in the memory that is assigned to it and there is a chance that the value
could contain ‘x’.
The comparative expression of the while loop is evaluated and checked to see if key
is not equal to (!=) ‘x’. When no key has been pressed by the user, the variable key is
not equal to ‘x’, it is equal to 0, so the answer to the question "Is key not equal to ‘x’?"
is true and the while loop is entered. Remember from Chapter 2 section 2.2.2 on page
48 that an individual character is written between single quotes in C.
Upon entering the loop, the value of the variable num is printed to the screen. This
variable was initialised to 50, so 50 is printed to the screen. The scanf() function is run
next and will stop the program while it waits for the user to press a key. When the user
does press a key, the value of the key is stored in the variable key and the next line of
code is run.
The if statement checks if the user pressed the ‘u’ key and if so, adds 1 to the variable
num. If the key pressed is not ‘u’, the program flow jumps to the else if statement.
The else if statement checks if the value of the key pressed is ‘d’. If it is, 1 is
subtracted from the variable num. Regardless of whether one of the previous statements
evaluated to true or false, program flow will continue at the top of the while loop where
the loop expression is evaluated again.
If the ‘x’ key was pressed, then the variable key would hold the character ‘x’. When
the loop expression is evaluated again, it would evaluate to false and the loop would be
exited. By exiting the loop, the program will run the printf() statement below the loop
and program flow would drop into the while(1) loop.
If none of the keys ‘u’, ‘d’ or ‘x’ were pressed, but some other key was pressed instead,
the variable key would now contain that character value, but the program would just
continue running without exiting the loop.
4.3 • The Guess My Number Game
The next program, called guess, uses the if – else construct inside a while loop.
Project name: guess
Project location: CH4\guess
main.c
● 76
Chapter 4 • The while Loop and Commenting Code
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
int guess;
UartInit();
printf("Try to guess my number:\r\n\r\n");
printf("Enter a number between 1 and 100: ");
scanf("%d", &guess);
while (guess != 74) {
if (guess > 74) {
printf("\r\nNumber is Lower.\r\n");
}
else {
printf("\r\nNumber is Higher.\r\n");
}
printf("Enter a number between 1 and 100: ");
scanf("%d", &guess);
}
printf("\r\nYou guessed the number!\r\n");
}
while(1) {
}
Example Program Output
Try to guess my number:
Enter a number between 1
Number is Higher.
Enter a number between 1
Number is Higher.
Enter a number between 1
Number is Lower.
Enter a number between 1
Number is Lower.
Enter a number between 1
You guessed the number!
and 100: 5
and 100: 50
and 100: 80
and 100: 75
and 100: 74
The object of this program is for the user to guess a number. When run, the program
prompts the user to enter a number between 1 and 100. The number is then compared
to a constant value inside the program. If the user’s number is higher or lower than the
programs value, a message is displayed to tell the user if his number is higher or lower. If
the number entered by the user matches the program’s number, a message is displayed
to say that the number guessed was correct.
To really make this program into a proper game, the program should generate a random
number for the user to guess, rather than have a fixed value programmed into it. We will
skip this step to keep the program simple.
An initial number is obtained from the user before evaluating the loop expression. If
the user guessed the correct number the first time, i.e. 74, the loop expression would
evaluate to false and the entire loop would be skipped over.
● 77
C Programming with Arduino
If the first number that the user entered was not correct, the while loop would be
entered into. The if – else construct is used to tell the user if the number was too high
or too low. The user is then asked to enter a new number at the bottom of the loop. If
the number is correct, the loop would be exited. If not the loop code will carry on running
until the user enters the correct number.
4.4 • Back to the Temperature Controller Example
In our temperature controller example from Chapters 2 on page 41 and Chapter 3 on
page 61, the temperature is read by the program and a message is displayed to either
turn on or off a heating element. The problem with the program is that the temperature
is only read once. We can now use a loop to continually monitor the temperature and
display the on or off message as the temperature changes.
Our next program, called montemp, will allow the user to increase or decrease the
temperature using the ‘u’ and ‘d’ keys on the keyboard. The program will keep monitoring
the temperature and if it increases above a threshold value will display a message to
switch the heater off. If below a threshold value the message to switch the heater on will
be displayed.
Project name: montemp
Project location: CH4\montemp
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
int temper = 30;
char key = 0;
UartInit();
printf("Press u to increase the temperature\r\n");
printf("Press d to decrease the temperature\r\n");
printf("Press x to quit the program\r\n");
printf("Temperature is %d deg. C
", temper);
while (key != 'x') {
scanf("%c", &key);
if (key == 'u') {
temper = temper + 1;
}
else if (key == 'd') {
temper = temper - 1;
}
printf("\r\nTemperature is
if (temper > 35) {
printf("Switch element
}
else if (temper < 30) {
printf("Switch element
}
else {
printf("Temperature is
}
}
printf("\r\nEnd of program.");
● 78
%d deg. C
", temper);
OFF.
");
ON.
");
correct. ");
Chapter 4 • The while Loop and Commenting Code
}
while(1) {
}
Example Program Output
Press u to increase the temperature
Press d to decrease the temperature
Press x to quit the program
Temperature is 30 deg. C u
Temperature is 31 deg. C Temperature is correct. u
Temperature is 32 deg. C Temperature is correct. u
Temperature is 33 deg. C Temperature is correct. u
Temperature is 34 deg. C Temperature is correct. u
Temperature is 35 deg. C Temperature is correct. u
Temperature is 36 deg. C Switch element OFF.
d
Temperature is 35 deg. C Temperature is correct. d
Temperature is 34 deg. C Temperature is correct. d
Temperature is 33 deg. C Temperature is correct. d
Temperature is 32 deg. C Temperature is correct. d
Temperature is 31 deg. C Temperature is correct. d
Temperature is 30 deg. C Temperature is correct. d
Temperature is 29 deg. C Switch element ON.
x
Temperature is 29 deg. C Switch element ON.
End of program.
There is nothing new to you in the montemp program, just more statements in the while
loop. Because the Arduino is not interfaced to the thermometer or temperature sensor
and heater element, the user must play the role of the heating system that the Arduino is
controlling. The user must control the heater element by changing the temperature using
the u and d keys and must respond to the on and off messages as a heater controlled by
an embedded system would.
The program is designed to keep a temperature between 30 and 35 degrees Celsius.
If the user (heater) increases the temperature above 35, the message to switch the
element off is displayed (the embedded systems switches the heater off). Of course it
is up to the user to play the part of the heater and start decreasing the temperature
when the heater is switched off by pressing the ‘d’ key a number of times to simulate the
temperature decrease of the system.
When the temperature is decreased below 30, a message is displayed to switch the
heater element on. It is up to the user to now increase the temperature as the heater
would and thus keep the temperature between the upper and lower limits (35°C and
30ºC).
When controlling the heater element with the embedded system connected to a heater
and temperature sensor, the program would be written to read the sensor continually and
if the temperature was too high, switch the element off. If the temperature was too low,
it would switch the element on. There would be no waiting in the loop as our example
program does – waiting for a key press from the user.
4.5 • Commenting Programs
Comments can be written into source code files by a programmer to explain what a
program does, as well as to add further helpful information to the code. These comments
● 79
C Programming with Arduino
are useful when the programmer wants to modify the program at a later date and may
have forgotten exactly what the program code does. If another programmer is going to
use or modify a program, comments in the source code will help him to understand the
code and the intentions of the original author of the code. Note that comments should
be used to explain what the program does, rather than explain how the programming
language works.
The next program called comment shows how comments are used.
Project name: comment
Project location: CH4\comment
main.c
#include <stdio.h>
#include "stdio_setup.h"
/* C style comment */
/*
Multi line C
style comment
*/
// C++ style comment
// Let's see how to comment a program:
/*-----------------------------------------------Project name: comment
File name:
main.c
This program converts kilometres to miles
------------------------------------------------*/
int main(void)
{
float kilos;
float miles;
/* stores distance in kilometres */
/* stores distance in miles
*/
UartInit();
/* prompt the user for distance in km's */
printf("\r\nEnter distance in kilometres: ");
scanf("%f", &kilos);
/* convert kilometres to miles */
miles = kilos * 0.62137;
/* show the user the result of the calculation */
printf("\r\nDistance is %.3f in miles.\r\n", miles);
}
while(1) {
}
Example Program Output
Enter distance in kilometres: 25
Distance is 15.534 in miles.
Comments are written between a forward slash and asterisk (/*) and an asterisk and
forward slash (*/) e.g.:
/* This is a comment */
● 80
Chapter 4 • The while Loop and Commenting Code
Comments included in the source code file and are ignored by the compiler. Comments
can be written across multiple lines in a source code file e.g.:
/* This comment
occurs on
multiple lines */
Most, if not all, modern C compilers allow the use of C++ comments. C++ comments can
only be used on a single line and start with a double forward slash (//). There is nothing
to terminate this style of comment, so everything to the right of the double forward slash
is part of the comment.
It is important to comment programs, particularly long ones. Commenting programs can
save you hours of frustration when you come back to a program months after writing it
and you can’t remember what it does or how some part of the program works.
Comments should include such things as which units variables are stored in. In the above
example, the comments clearly state what the variables are storing (distance) and in
which units they are being stored (miles and kilometres). Other information can also be
added to a file in the form of comments such as who the author of the file is, the date
it was written, when it was modified, who modified it, details of the hardware that the
program was written to run on, etc.
The example programs in this book will contain few comments to save space in the book
and to help keep the code uncluttered. Some of the later more advanced programs will
show good use of comments.
4.6 • Programming Style
Programming style refers to how a program is written in terms of where brackets and
braces are placed, how the program is indented, how many spaces are used for indenting
and other details. As an example, the programs that you have seen in this book have
been indented by four spaces. Indenting refers to moving of statements or lines of code
to the right inside a block of code. The Tab key is used to indent code, and depending
on the code editor program, can be set to either insert a Tab character or insert space
characters.
4.6.1 • Tab Settings in Atmel Studio
In Atmel Studio, the editor is set to insert Tab characters by default and display them
with a width of 4 spaces. Settings for the number of spaces to use for indenting and
whether to use Tab characters or spaces can be set in Atmel Studio from the main menu
under Tools → Options... and then expanding items from the left pane of the dialog box
that pops up. Expand Text Editor → GCC and then click Tabs as shown in Figure 4-2 on
page 82.
4.6.2 • Choosing Tab Width
The default tab width of four spaces is a good choice which makes the code easy to read
as the indenting is clearly visible without being too deep. Traditionally the tab character
used to always be eight characters wide and many old command line text editors use
this convention. It is commonly accepted for a tab to be set to a width of between two to
eight characters in most programming languages.
● 81
C Programming with Arduino
Figure 4-2 Tab Settings in Atmel Studio
4.6.3 • Code Indenting Examples
The following program, called style1, has no indenting, but will compile and run without
any problems.
Project name: style1
Project location: CH4\style1
main.c
/* style1.c */
/* a program without any indenting */
/*
The program displays a count to the screen.
Counts from 0 to 15, skipping 10
*/
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
UartInit();
int count = 0;
printf("Counting to 15, skipping 10:\r\n");
while (count <= 15) {
/* if the number is 10, skip over it */
if (count != 10) {
printf("Count is: %d\r\n", count);
}
count = count + 1;
}
printf("Finished.\r\n");
while(1) {
}
}
● 82
Chapter 4 • The while Loop and Commenting Code
The above program becomes easier to read when it is indented as shown in style2.
Project name: style2
Project location: CH4\style2
main.c
/* style2.c */
/* A program with indenting */
/*
The program displays a count to the screen.
Counts from 0 to 15, skipping 10
*/
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
int count = 0;
UartInit();
printf("Counting to 15, skipping 10:\r\n");
while (count <= 15) {
/* if the number is 10, skip over it */
if (count != 10) {
printf("Count is: %d\r\n", count);
}
count = count + 1;
}
printf("Finished.\r\n");
}
while(1) {
}
Program Output of style1.c, style2.c and style3.c
Counting
Count is:
Count is:
Count is:
Count is:
Count is:
Count is:
Count is:
Count is:
Count is:
Count is:
Count is:
Count is:
Count is:
Count is:
Count is:
Finished.
to 15, skipping 10:
0
1
2
3
4
5
6
7
8
9
11
12
13
14
15
This program can even be written on a single line and will still work. See style3 included
with the example programs from the accompanying files and found in the project folder
CH4\style3. The file will compile and run just as the two previous examples did. (Scroll to
● 83
C Programming with Arduino
the right in the C source file of style3 to see the entire program.)
4.6.4 • Other Aspects of Programming Style
Other aspects of style refer to where you place braces, as the next examples show around
an if statement.
As you have already seen:
if (count != 10) {
printf("Count is: %d\n", count);
}
Some programmers prefer this style:
if (count != 10)
{
printf("Count is: %d\n", count);
}
The next example is only legal if there is a single statement under the if. Some
programmers prefer this and will then choose one of the above styles if there is to be
more than one statement under the if.
if (count != 10)
printf("Count is: %d\n", count);
This is also legal for a single statement:
if (count != 10) printf("Count is: %d\n", count);
And this:
if (count != 10) {printf("Count is: %d\n", count);}
Some parts of coding style are just personal preferences, others are required to make a
program clear, easy to read and understand. Bear this in mind when developing your own
programming style.
Consistency is very important after choosing a programming style. Make sure that all
of your code uses the same style and formatting throughout each source code file and
project.
4.7 • Exercises
4.7.1 • Subtract
Write a program called subtract that asks the user for two numbers. The second number
must be subtracted from the first and the result displayed. Use a loop to ask the user to
either quit the program or do another calculation.
● 84
Chapter 4 • The while Loop and Commenting Code
4.7.2 • Program Loop
The example program pumpctrl from the previous chapter has a problem in that the
program exits after a selection is made by the user. Modify this program to run inside a
loop and continually ask the user for an option. Add an extra menu option that the user
can use to exit the program.
4.8 • Solutions
4.8.1 • Solution to 4.7.1
Project name: CH4_1_subtract
Project location: Exercises\CH4_1_subtract
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
int num1, num2;
char key = 0;
UartInit();
printf("Subtraction program\r\n");
while (key != 'x') {
printf("\r\nEnter a number: ");
scanf("%d", &num1);
printf("\r\nEnter a number to subtract: ");
scanf("%d", &num2);
printf("\r\n%d - %d = %d\n", num1, num2, num1 - num2);
printf("\r\nPress any key to subtract again\r\n");
printf("or x to quit\r\n");
scanf("%c", &key);
// read extra character
scanf("%c", &key);
// read the actual key press
}
printf("\r\nEnd of program.\r\n");
}
while(1) {
}
The above solution to the exercise is one possible solution. You could have used floating
point variables if you chose to and used a different key press to quit the program. Note
that the subtraction can be done in the printf() statement. The program creates a
variable internally to hold the result of the subtraction in this case. You could of course
create a variable to hold the result of the calculation.
An extra character is received by the program from the terminal when Enter is pressed
after the second number is typed in. This causes the last scanf() function in the loop to
receive the character and go back to the top of the loop to start another subtraction. A
second scanf() function is needed above the last scanf() to absorb the extra character.
● 85
C Programming with Arduino
4.8.2 • Solution to 4.7.2
Project name: CH4_2_pumpctrl_loop
Project location: Exercises\CH4_2_pumpctrl_loop
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
int input = 0;
UartInit();
while (input != 5) {
printf("Enter 1 to start pump\r\n");
printf("Enter 2 to start mixer\r\n");
printf("Enter 3 to stop pump\r\n");
printf("Enter 4 to stop mixer\r\n");
printf("Enter 5 to exit\r\n");
scanf("%d", &input);
if (input == 1) {
printf("Pump started.\r\n");
}
else if (input == 2) {
printf("Mixer started.\r\n");
}
else if (input == 3) {
printf("Pump stopped.\r\n");
}
else if (input == 4) {
printf("Mixer stopped.\r\n");
}
else if (input == 5) {
printf("Exiting.");
}
else {
printf("Invalid option\r\n");
}
}
printf("\r\nEnd of program.\r\n");
}
while(1) {
}
4.9 • Summary
● 86
•
A loop can be used to alter the flow of a program and to run program
statements more than once.
•
The while loop will loop through the lines of code contained between its braces
as long as the loop expression remains true.
•
After the last line of code in the loop has been executed, the loop expression
will be evaluated again.
•
A loop can be used to continually monitor an external parameter, such as
temperature, in an embedded system.
Chapter 4 • The while Loop and Commenting Code
•
Comments can be added to source code to explain what a program does as well
as to include other information such as who the author of the program is, when
it was created, what units a variable is in, etc.
•
Programming style is a matter of personal choice – use it wisely.
•
The main objective when developing your personal style of programming
should be to make programs simple, clear, and easy to read and understand.
•
If you work for a software company, they may have a styles guideline
document that will outline the preferred style that they would have you use
when programming for them.
This chapter covered the while loop only. We will see that other kinds of loops are
available in C later in the book. The next chapter looks at functions, which are an
important part of C programming.
● 87
C Programming with Arduino
● 88
Chapter 5 • Functions
Chapter 5 • Functions
What you will do in this chapter:
•
Learn how to write functions
•
Use preprocessor directives
•
Learn about header files
•
See how functions relate to linking and library files
You have already used the printf() and scanf() functions. You have also written
a function each time that you have written a C program – the function main(). The
printf() function was supplied with the GCC toolchain that is installed with Atmel Studio.
The actual C code that makes up this function is rather large and complex as the function
has a lot of text formatting capabilities. If you had to write this code each time you
wanted to print text to the screen, programs would be very large, hence the advantage of
writing functions – the code for the function is written once and the function is used many
times over.
Another advantage of using functions is that your program can be split over multiple files.
If a large program is stored in a single file, it would become very difficult to read and
maintain. By breaking a program up into functions it can be split across two or more files.
When splitting up a program into files, similar functions can be grouped together. You
may decide to save all of the functions to do with serial communications into a file called
serial.c and the functions to do with timers to timer.c.
Code can also be easily reused when it is divided up into separate files. For a particular
piece of hardware, or algorithm you only need to write the functions once and then
include the file that contains the functions in each program that you wish to write that
uses them. For example we used printf() in all of our programs without ever writing the
printf() function. If printf() had been a function that you had written, you would only
have to create it once and then use it whenever you wanted to.
Technical Terms
Algorithm – An algorithm is a method or set of tasks for solving a problem. An example
of an algorithm is code used to recognise a fingerprint scanned in by a fingerprint
scanner. We would refer to the method used in this code that processes, compares and
recognises a fingerprint as a fingerprint recognition algorithm.
5.1 • Your Second Function
This will be the second function that you are going to write as you have already written
the function main() each time that you have written a C program. The program
countfunc shows how to write a function.
● 89
C Programming with Arduino
Project name: countfunc
Project location: CH5\countfunc
main.c
#include <stdio.h>
#include "stdio_setup.h"
void Count(void);
/* function prototype */
int main(void)
{
UartInit();
printf("Counting to 5\r\n");
Count();
/* call the function */
printf("Counting to 5 again\r\n");
Count();
/* call the function */
}
while(1) {
}
/* the function */
void Count(void)
{
int count = 1;
}
while (count <= 5) {
printf("%d\r\n", count);
count = count + 1;
}
/* count from 1 */
/* count to 5 */
/* display the count */
/* increment the count */
Program Output
Counting to 5
1
2
3
4
5
Counting to 5 again
1
2
3
4
5
In this example, a function called Count() is created and then called twice in the main
program as shown in Figure 5-1 on page 91. The function simply prints out a count
of 1 to 5. Near the top of the source file is the "function prototype" terminated with a
semicolon i.e. void Count(void);. The function prototype tells the compiler that this
function will be used in the program, similar to defining a variable that will be used in
a program. The main() function is a special function that does not require a function
prototype.
The actual function is written below the closing brace of the main() function. The body of
this function is contained between opening and closing braces. The use of the function in
● 90
Chapter 5 • Functions
this example saves the programmer from having to write the same code twice to print out
the count sequence. The function is written once and can be called any number of times
in the program.
The first use of the C keyword void in front of the function name tells the compiler that
the function does not return a value. The second use of the keyword void between the
parentheses of the function tells the compiler that the function takes no arguments. We
will see what this means in the examples that follow.
Figure 5-1 How a Function is Called
5.2 • Passing Data to a Function
Data can be sent to a function by using arguments. The function is then able to operate
on the data as the next program passcount shows.
Project name: passcount
Project location: CH5\passcount
main.c
#include <stdio.h>
#include "stdio_setup.h"
void Count(int num);
int main(void)
{
UartInit();
printf("Counting to 2\r\n");
Count(2);
printf("Counting to 10\r\n");
Count(10);
/* count to 2 */
/* count to 10 */
● 91
C Programming with Arduino
}
while(1) {
}
void Count(int num)
{
int count = 1;
}
while ( count <= num) {
printf("%d\r\n", count);
count = count + 1;
}
/* count from 1 */
/* count to num */
/* display the count */
/* increment the count */
Program Output
Counting to 2
1
2
Counting to 10
1
2
3
4
5
6
7
8
9
10
This program is very similar to the previous one except that the value that the function
is to count to is passed to it as an argument. When the Count() function is called for the
first time, a value of 2 is passed to it (the function takes an argument). The value of 2 is
stored in the variable num in this function. When this variable is used in the function, the
count is printed to the screen and the while loop exited when a count greater than 2 is
reached.
When Count() is called the second time, 10 is passed to it and the while loop inside
this function counts to the new value of num which is now 10. The variable between
parentheses of the function takes on the value that is passed to it – in this example the
variable num which is of type int.
5.3 • Passing More Than One Value to a Function
In the previous example, a single value was passed to a function (the function took
a single argument). More than one value can be passed to a function as the twoargs
program shows, which contains a function that takes two arguments.
● 92
Chapter 5 • Functions
Project name: twoargs
Project location: CH5\twoargs
main.c
#include <stdio.h>
#include "stdio_setup.h"
void Count(int num, int skip);
int main(void)
{
UartInit();
printf("Counting to 5, skipping 3\r\n");
Count(5, 3);
/* count to 5, skip 3 */
printf("Counting to 12, skipping 10\r\n");
Count(12, 10);
/* count to 12, skip 10 */
}
while(1) {
}
void Count(int num, int skip)
{
int count = 1;
}
while ( count <= num) {
if (count != skip) {
printf("%d\r\n", count);
}
count = count + 1;
}
/* count from 1 */
/* count to num */
/* display the count */
/* increment the count */
Program Output
Counting to 5, skipping 3
1
2
4
5
Counting to 12, skipping 10
1
2
3
4
5
6
7
8
9
11
12
In this program, two values are passed to the Count() function. The first value is passed
to it in the variable num and the second in the variable skip. The variable num is used
by the function as the value to count up to and the variable skip contains the number
that the function will skip over when printing out the count sequence. When calling the
function it is important to put the two numbers that are to be passed to the function in
● 93
C Programming with Arduino
the correct order. If the first call to the function had been Count(3, 5); then the num
variable would have been assigned the value 3 and the skip variable the value 5. In this
case the function would just count to 3 as there is not a value of 5 in this sequence to
skip over.
As you have seen in the previous two examples, values are passed to a function by
defining variables between the functions parentheses. If more than one value is to be
passed to the function, the variables are separated by commas. A function that is to
receive four values (take four arguments), for example, will be written like this:
void MyFunc(int val1, int val2, float val3, char val4)
{
// the integers val1 and val2 can be used by the function
// the floating point variable val3 can be used by the function
// the character variable val4 can be used by the function
}
As this example shows, the type of data to be passed to the function is specified when
declaring the function arguments. Each of these different data types are passed to the
function and can be used by the function. Each new variable to be passed to the function
between parentheses is separated by a comma.
When calling this function from within a program, values passed to it must be in the
correct order (two ints first, one float and then one char):
MyFunc(100, 20, 43.45, 'z');
5.4 • Passing a Variable to a Function
In the preceding examples, we passed constant values to the functions. A variable value
can also be passed to a function as vararg shows.
Project name: vararg
Project location: CH5\vararg
main.c
#include <stdio.h>
#include "stdio_setup.h"
void Message(char ch);
int main(void)
{
char key = 0;
UartInit();
printf("Press a key\r\n");
printf("Press x to exit\r\n");
while (key != 'x') {
/* exit if x key pressed */
scanf("%c", &key);
/* get key press from user */
Message(key);
/* display message */
}
printf("End of program.");
● 94
Chapter 5 • Functions
}
while(1) {
}
void Message(char ch)
{
if (ch == 'x')
printf("\r\nBye!\r\n");
else
printf("\r\nYou pressed %c\r\n", ch);
}
Example Program Output
Press a key
Press x to exit
a
You pressed a
b
You pressed b
t
You pressed t
y
You pressed y
x
Bye!
End of program.
A character variable key is passed to the Message() function in this example. The value of
key is copied into the function’s variable ch when the function is called.
5.5 • Getting a Value Back from a Function
Functions can return a value, meaning we can get a value back from a function. The type
of value being returned must be specified (e.g. int, float, char) and can be returned by
the function by using the return keyword as retval demonstrates.
Project name: retval
Project location: CH5\retval
main.c
#include <stdio.h>
#include "stdio_setup.h"
int prompt(void);
int main(void)
{
int user_num;
UartInit();
user_num = prompt();
printf("You entered %d\r\n", user_num);
while(1) {
}
● 95
C Programming with Arduino
}
int prompt(void)
{
int num;
}
printf("Please enter an integer number:\r\n");
scanf("%d", &num);
return num;
Example Program Output
Please enter an integer number:
100
You entered 100
When the prompt() function is called, it asks the user of the program to enter an integer
number. The number is then stored in the local variable num (local to the function, it can’t
be "seen" by the main() function). The number is then returned to the main() function
by using the return keyword, where it is assigned to the variable user_num using the
assignment (=) operator. The return type of the function is specified by placing the
variable type keyword in front of the function name, in this case, int.
5.6 • Passing Values to a Function and Receiving a Value Back
The next example program addfunc receives two values passed to it and returns a value.
Project name: addfunc
Project location: CH5\addfunc
main.c
#include <stdio.h>
#include "stdio_setup.h"
float AddNum(float num1, float num2);
int main(void)
{
float add_result;
UartInit();
printf("Adding two numbers:\r\n");
add_result = AddNum(10.34, 100.56);
printf("Result is %.2f", add_result);
}
while(1) {
}
float AddNum(float num1, float num2)
{
float result;
}
● 96
result = num1 + num2;
return result;
Chapter 5 • Functions
Program Output
Adding two numbers:
Result is 110.90
A function called AddNum() is used to add two numbers together and return the result
of the addition. Floating point numbers are used throughout. The return type of the
function is specified as floating point and the two numbers passed to the function are also
specified as floating point. In this example you could, of course, have more easily just
added the numbers together in main() without creating a function.
More than one value can be returned from a function, but you will have to wait until we
see how pointers work in a later chapter.
5.7 • Flashing LED Simulation Program
The flashled program simulates a flashing LED by displaying an on or off message
instead of switching an LED on or off. When using a real LED, the flow of the program will
be exactly the same; the only difference being the code that displays the messages will
be replaced by code that switches the LED on and off. A function is called to use up some
time while the LED is either on or off. If this function was not present on a system using
a real LED, the LED would flash so fast that it would look like it was permanently on,
although it might look a bit dimmer.
Project name: flashled
Project location: CH5\flashled
main.c
#include <stdio.h>
#include "stdio_setup.h"
void Delay(void);
int main(void)
{
UartInit();
}
while (1) {
/*
printf("ON\r\n"); /*
Delay();
/*
printf("OFF\r\n");
/*
Delay();
/*
}
continuous loop */
switch LED on */
leave LED on for a while */
switch LED off */
leave LED off for a while */
void Delay(void)
{
volatile long count = 1000000;
}
/* waste time */
while (count) {
count = count - 1;
}
● 97
C Programming with Arduino
Program Output Sample
ON
OFF
ON
OFF
ON
The Delay() function works by counting to a very big value which uses up processor
time. This leaves the message displayed on the screen for the duration of the delay
before a new message is displayed. Changing the value assigned to count in the Delay()
function will change the waiting time between messages sent to the terminal emulator.
The volatile keyword is used when defining the count variable to stop the default
optimisation setting of the compiler from optimising the delay loop away. Compiler
optimisation sees that the variable count is not used in any program output and so
removes it and the loop that it is used in. Optimisation and the volatile keyword is still
to be explained in more detail.
5.7.1 • The while(1) Loop
As already mentioned, when an embedded system program has finished running, unlike
a PC, it has no operating system to return to. You will typically want to have the program
running continually and never exit. If you were using a calculator for example, you would
not want the calculator program to finish and then stop. You would want it to be ready for
you to enter a new calculation each time.
The simplest way of stopping a program from terminating is to write the body of the
program in a while(1) loop. This is just a while loop that always evaluates to true
because it has 1 as its loop expression. The typical simple embedded program will start
by initialising any hardware used by the program upon entry into the C code. It will then
enter the while(1) loop to run the bulk of the program continually. This is what the
flashled program does by having the main program code inside the while(1) loop.
For those who are familiar with the Arduino IDE, the code above the while(1) loop is the
same as code put in the Arduino setup() function – this code only runs once when the
Arduino is powered on or a new program is loaded. Code placed inside the while(1) loop
is the same as code inserted in the Arduino loop() function – the code runs continually in
a loop.
5.7.2 • The long Data Type
A new data type called long is used in the Delay() function of the flashled program. This
data type is an integer, but can hold a bigger number than the int data type. It can also
be written as long int in the code as follows.
long int count = 1000000;
In the flashled program, an int was not big enough to hold the number 1000000, making
it necessary to use the long data type. These two data types vary in size, depending on
the microcontroller data bus width e.g. 8-bit or 32-bit. If this was a 32-bit microcontroller
that the code was running on, an int would work and a long would not be necessary for
the count variable. Data types and sizes will be explained in more detail after looking at
binary numbers.
● 98
Chapter 5 • Functions
5.8 • Preprocessor Directives
You have already seen the #include preprocessor directive in each program that you
have written. The #include preprocessor directive is used to include a header file (a
text file with a .h file extension). These files contain the function prototypes for functions
such as printf() and scanf(). As you have learned, each function must have a function
prototype. By including the header files, you include the function prototypes for the
standard library functions that are part of the C language standard. Using the #include
preprocessor directive to include a header file is like copying the contents of the header
file and pasting them into the top of your C source file.
The C standard library containing the standard library functions has a number of header
files associated with it. Functions from the library file are linked with your object file
during the linking process. The header files contain the function prototypes of the library
functions that you use from the library file.
Function prototypes for printf() and scanf() can be found in the stdio.h header file.
The name stdio stands for standard I/O or standard input/output. Both printf() and
scanf() are standard I/O functions. These are not the only functions in the library or
function prototypes in the header files, there are a number of other functions available.
While on the subject of names, the functions that you have been using from the C
standard library are shortened names that describe the function:
printf() = print formatted (there are lots of formatting options that can be used with
this function like printing floats or ints, etc.).
scanf() = scan formatted
Back to preprocessor directives – there are additional preprocessor directives other than
#include. Each preprocessor directive starts with the hash (#) symbol.
The #define preprocessor directive can be used to name a constant value used in a C
program. For example the line:
#define MY_NUM 100
When this preprocessor directive is placed at the top of a C source file, it will enable you
to use the text MY_NUM everywhere that you wish to use the value 100. If the value ever
needs to be changed, it only needs to be changed in one place – where it is defined, and
the new number will be updated in the code wherever the name MY_NUM is used.
The name defined by the #define directive is usually written in upper-case letters by
most C programmers by convention, although it is possible to use any combination
of upper and lowercase letters. The rules that apply to variable names when defining
variables also apply to names defined with #define – the name must start with an
alphabet or underscore character, etc.
In the defdel program, the previous program has been modified to define the delay count
value using a preprocessor directive as shown in the code listing below.
Project name: defdel
Project location: CH5\defdel
main.c
#include <stdio.h>
#include "stdio_setup.h"
#define DEL_CNT 100000
● 99
C Programming with Arduino
void Delay(volatile long count);
int main(void)
{
UartInit();
printf("\x1B[2J");
/* clear screen */
printf("\x1B[?25l");
/* hide cursor */
}
while(1) {
printf("ON \r");
Delay(DEL_CNT);
printf("OFF\r");
Delay(DEL_CNT);
}
/*
/*
/*
/*
switch LED on */
leave LED on for a while */
switch LED off */
leave LED off for a while */
void Delay(volatile long count)
{
/* waste time */
while (count) {
count = count - 1;
}
}
Program Output
The program sends commands to the terminal window to clear the screen and hide the cursor.
ON and OFF text is displayed in the top left of the terminal window instead of printing on a new line.
Besides using the #define preprocessor directive, the program has also been modified to
send commands to the terminal to tell it to clear the window and hide the cursor. When
the ON and OFF text is printed, it is followed by a carriage return character only which
causes the now invisible cursor to move to the beginning of the current line so that new
text printed overwrites the old text on the line.
The Delay() function has been modified to take the delay count value as an argument.
The #define directive has been used to define DEL_CNT to the value of 100000. 100000
will now be substituted into the program everywhere that DEL_CNT is used before the
file is compiled. This is done by the preprocessor. The preprocessor is either a separate
program that is run before the compiler or a process that is part of the compiler but is
run before compiling. The preprocessor will substitute header files into the C source file
when it finds #include directives and will substitute the constant value in place of the
name that it was defined as using the #define directive wherever it finds the name in the
source code.
The preprocessor will change the code below:
int main(void)
{
UartInit();
}
while(1) {
printf("ON \r");
/*
Delay(DEL_CNT);
/*
printf("OFF\r");
/*
Delay(DEL_CNT);
/*
}
● 100
switch LED on */
leave LED on for a while */
switch LED off */
leave LED off for a while */
Chapter 5 • Functions
So that the compiler sees:
int main(void)
{
UartInit();
}
while(1) {
printf("ON \r");
Delay(100000);
printf("OFF\r");
Delay(100000);
}
/*
/*
/*
/*
switch LED on */
leave LED on for a while */
switch LED off */
leave LED off for a while */
The #define directive used in the above program enables the programmer to change
the value passed to the Delay() function at the top of the source file, no matter where
or how many times it is used in the program. This saves the programmer from having to
search for each occurrence of the constant value in the program in order to change it. It
can be changed once at the top of the file.
Terminal Control Codes
Control codes, in the form of escape sequences, can be sent to the terminal program in
order to change its behaviour and settings as the previous C program demonstrates by
clearing the screen and hiding the cursor. Control codes are recognised by the terminal
program as they all start with the escape code hexadecimal number 1B, which can
be sent as part of a string in C using the hexadecimal escape sequence \x1B as the
printf() statements do in the previous program. The escape code is followed by a
terminal command, for example:
Clear Screen
Hide Cursor
Show Cursor
– [2J
– [?25l
– [?25h
Hexadecimal numbers are explained in the next chapter on number systems and escape
sequences are still to be looked at in more detail.
Another use of the #define directive is to define a mathematical constant. An example
is the constant PI (π) used in mathematics that has a value of 3.14159 when rounded to
five decimal places. A program using this value becomes a lot more self explanatory when
using the letters PI in place of the number 3.14159.
The mathematical formula for calculating the area of a circle is:
A = πr²
where:
A = area
π = 3.14159
r = radius of the circle (distance from the centre of the circle to the edge)
r² means r × r
This formula would be written in C as:
area = 3.14159 * radius * radius;
● 101
C Programming with Arduino
Predefined Constants
PI is actually already defined in the header file math.h and will be available in any C
source file that includes math.h using #include <math.h> at the top of the C file. PI is
defined as M_PI in math.h and is shown below with some other predefined constants from
math.h.
#define M_PI
3.14159265358979323846
/* pi */
#define M_PI_2
1.57079632679489661923
/* pi/2 */
#define M_E
2.7182818284590452354
/* the function e */
#define M_SQRT2
1.41421356237309504880
/* the square root of 2 */
circle shows how to define PI to make a program more readable.
Project name: circle
Project location: CH5\circle
main.c
#include <stdio.h>
#include "stdio_setup.h"
#define PI
3.14159
float CircArea(float radius);
int main(void)
{
float radius;
UartInit();
printf("Enter radius of circle: ");
scanf("%f", &radius);
printf("\r\nArea of circle is %.3f", CircArea(radius));
}
while(1) {
}
float CircArea(float radius)
{
return PI * radius * radius;
}
Program Output
Enter radius of circle: 23.5
Area of circle is 1734.943
PI is defined using the #define directive as previously described and used in the
CircArea()function to calculate the area of a circle when given the radius of the circle.
Previously you always defined a variable when doing a calculation to hold the result of the
● 102
Chapter 5 • Functions
calculation. If you look at the CircArea() function, no variable has been defined in which
to store the result of the area calculation before it is returned by this function. This will
still work in C. A place to store the calculation result before returning it is still needed, but
when a program is written like this, a temporary variable is created in the program and is
not seen by the programmer.
Another new thing in this program is calling the CircArea() function directly in the
printf() function. This is also perfectly legal in C and a temporary variable will again be
created for you and passed to the printf() function when you write a program in this
way.
Notice also that a floating point variable radius is defined in main() and a floating point
variable named radius is also defined as an argument of the function CircArea(). In
C you can not have two variables of the same name in a program, but because the two
variables are defined in two separate functions, it is not a problem that they have the
same name. The code running in main() and using the variable radius does not know
about the variable with the same name in the CircArea() function. This is called the
scope of the variable. The variables defined in main() can only be "seen" and used in
main(). The variables defined in any other function can only be "seen" and used in that
function. When the CircArea() function is called in the above program, the variable
radius in main() is copied into the CircArea() function’s variable radius as would occur
if the variables had different names.
5.9 • Functions Calling Functions
You can write functions that call other functions, which is useful when breaking up a very
big program into smaller parts to make it easier to read and manage. The main()
function has already been used to call library functions as well as other functions that you
have written. The program that follows will clarify. You don’t have to type this program in,
it is only for illustrative purposes and does nothing.
void a(void);
void b(void);
int main(void)
{
a();
b();
}
void a(void)
{
b();
}
// prototype for function a
// prototype for function b
// main() calls function a()
// main() calls function b()
// function a() calls function b()
void b(void)
{
}
5.10 • Using Multiple Source Files
A C program can be split up into multiple source code files by using functions. Functions
used in a program do not have to exist in the same source file as main(). One reason
for splitting up a program into more than one file is to make a large program modular
by grouping similar functions together with each group in its own file. A large program
becomes more maintainable this way than putting all of the source code in a single file.
Another reason is to easily reuse source code by copying the files that contain the desired
● 103
C Programming with Arduino
functions into another project.
The splitprog project below consists of two C source code files: main.c containing the
function main() and splitfunc.c containing a function used by main(). To create the
splitprog project, start a new project from the template file as you have been doing with
the previous projects. Name the project splitprog, and type the code from the first code
listing below into main.c. Save the file.
5.10.1 • Adding a New C Source File to an Atmel Studio Project
To add a new source code file to an Atmel Studio project, right-click the project name in
Solution Explorer then select Add → New Item... from the pop-up menu as shown in
Figure 5-2 on page 105.
In the Add New Item dialog box, click C File and then give the new C file a name in the
Name: field at the bottom of the dialog box. For our splitprog project, the name of the file
is splitfunc.c as shown in Figure 5-3 on page 106. Click the Add button when done.
5.10.2 • Project with Two C Source Code Files
Project name: splitprog
Project location: CH5\splitprog
main.c
/* splitprog project main.c file */
#include <stdio.h>
#include "stdio_setup.h"
/* prototype for function in splitfunc.c */
int FindBiggest(int num1, int num2);
int main(void)
{
int first, second;
UartInit();
printf("Program determines which number is bigger.\r\n");
printf("Enter an integer number: ");
scanf("%d", &first);
printf("\r\nEnter a 2nd integer number: ");
scanf("%d", &second);
printf("\r\n%d is bigger", FindBiggest(first, second));
}
while(1) {
}
The second file:
● 104
Chapter 5 • Functions
splitfunc.c
/* splitfunc.c */
/* function returns the bigger of two numbers */
int FindBiggest(int num1, int num2)
{
if (num1 > num2) {
return num1;
}
else {
return num2;
}
}
Example Program Output
Program determines which number is bigger.
Enter an integer number: 99
Enter a 2nd integer number: 81
99 is bigger
Figure 5-2 Adding a New Item to an Atmel Studio Project
The new C source file will be opened for editing in Atmel Studio and will appear in the
Solution Explorer pane as part of the project. Type the code from the second listing into
the new source file. Build and load the program to the Arduino as done with previous
projects.
● 105
C Programming with Arduino
During the build process of the splitprog project, two object files will be produced – one
for each C source file. The linker will link the two object files together with the necessary
library files to produce the executable output file.
Figure 5-3 Adding a New C File to an Atmel Studio Project
5.11 • Header Files
In the previous example, you could put the function prototype of the FindBiggest()
function into a header file. The header file would then be included in the file containing
main() by using the #include directive. The function prototype would then not have
to be written at the top of the file containing main(). A header file called splitfunc.h
would be added to the project in the same way that the C file was added, but by choosing
Include File from the Add New Item dialog box from Figure 5-3 on page 106.
splitfunc.h would contain the function prototype:
int FindBiggest(int num1, int num2);
The first few lines of main.c of the splitprog project would now contain:
// main.c
#include <stdio.h>
#include "stdio_setup.h"
#include "splitfunc.h"
int main(void)
...
...
Note that when including your own header file that is part of a project, the header file
name is placed between inverted commas (" "). When using standard library header files,
the header file name is placed between angle brackets (<>). These angle brackets are the
same characters used as less than and greater than signs in C code.
Using a header file to contain a single function prototype as above is not very useful. When
a C source file contains many functions, it makes much more sense to use a header file
for the function prototypes. For a source file containing ten functions, you will create the
header file and then include it in every source file that uses one or more of the functions.
● 106
Chapter 5 • Functions
5.12 • How Functions Relate to Linking and Library Files
After seeing how a program can be broken up into functions, you should now be able
to understand the big picture of how the C tools work. Library files that are linked with
your programs were originally written in C and then compiled. The object files that
were produced by compiling the library C source code files are all grouped together
into library files using a utility program that comes with the GCC toolchain. When the C
tools are installed on your system during the Atmel Studio installation, these library files
are installed along with header files that contain the function prototypes of the library
functions.
To use a function from one of these library files, you include the header file that contains
the function prototype in your C source file and use the linker settings to link the
corresponding library into your program when it is built. Creating a new Atmel Studio
project from scratch includes some libraries by default. The template file for this book
used to create a new project also contains linker settings to include specific libraries
making in unnecessary to add these libraries to the linker settings for the projects that
have been built so far.
5.13 • Exercises
5.13.1 • Parallel Resistor Calculation
Write a function that calculates the resistance of two resistors in parallel using the
following formula:
Write a program that uses the function in order to test it and demonstrate that it works.
5.13.2 • Resistor Formula C File
Create a project in Atmel Studio that has the usual main.c file and a separate file called
resistance.c that contains the function from the previous exercise that calculates the
resistance of two resistors in parallel. Add a function to the resistance.c file that calculates
the resistance of up to three resistors in parallel where the third parameter passed to
the function can be zero when calculating the value of two resistors in series. Create a
file called resistance.h in the project that contains the function prototypes for the two
resistance calculations. Write code in main() that uses the two resistance calculation
functions to demonstrate that they work.
The resistance of resistors in series is calculated by adding the resistances together, for
example:
● 107
C Programming with Arduino
5.14 • Solutions
5.14.1 • Solution to 5.13.1
Project name: CH5_1_parallel_resistors
Project location: Exercises\CH5_1_parallel_resistors
main.c
#include <stdio.h>
#include "stdio_setup.h"
float ParResistance(float R1, float R2);
int main(void)
{
UartInit();
printf("470 ohms in parallel with 1k is: %.2f ohms\r\n", ParResistance(470.0, 1000.0));
printf("2k2 in parallel with 2k2 is: %.2f ohms\r\n", ParResistance(2200.0, 2200.0));
printf("4k7 in parallel with 10k is: %.2f ohms\r\n", ParResistance(4700.0, 10000.0));
}
while(1) {
}
float ParResistance(float R1, float
{
float res_mult;
// result
float res_add;
// result
float res_div;
// result
R2)
of resistor multiplication
of resistor addition
of resistor division
res_mult = R1 * R2;
res_add = R1 + R2;
res_div = res_mult / res_add;
}
return res_div;
Program Output
470 ohms in parallel with 1k is: 319.73 ohms
2k2 in parallel with 2k2 is: 1100.00 ohms
4k7 in parallel with 10k is: 3197.28 ohms
In the above solution, the ParResistance() function is written out in a way that stores
each intermediate part of the calculation in its own variable. This is probably the longest
way to write this function which could be written in a much shorter form as shown below.
float ParResistance(float R1, float R2)
{
return R1 * R2 / (R1 + R2);
}
In the short version of the function, the addition part must be placed in parentheses to
force the result of the multiplication to be divided by the result of the addition, otherwise
the result of the multiplication will be divided by the value of R1 and then the value of R2
added to it afterwards.
● 108
Chapter 5 • Functions
This has to do with operator precedence which is explained in detail in section 12.3 of
Chapter 12 on page 241. In most cases the same C program can be written in several
different ways to achieve the same result. Aim to create C programs that are easy to read
and understand as well as efficient in their use of memory and processing time.
5.14.2 • Solution to 5.13.2
Project name: CH5_1_resistor_calculations
Project location: Exercises\CH5_1_resistor_calculations
main.c
#include <stdio.h>
#include "stdio_setup.h"
#include "resistance.h"
int main(void)
{
UartInit();
printf("100 ohms and 470 ohms in parallel: %.2f ohms\r\n",
ParResistance(100.0, 470.0));
printf("100 ohms, 2k2 and 4k7 in series: %.2f\r\n",
SerResistance(100.0, 2200.0, 4700.0));
printf("220 ohms and 6k8 in series: %.2f\r\n",
SerResistance(220.0, 6800.0, 0.0));
}
while(1) {
}
resistance.c
float ParResistance(float R1, float R2)
{
return R1 * R2 / (R1 + R2);
}
float SerResistance(float R1, float R2, float R3)
{
return R1 + R2 + R3;
}
resistance.h
#ifndef RESISTANCE_H_
#define RESISTANCE_H_
float ParResistance(float R1, float R2);
float SerResistance(float R1, float R2, float R3);
#endif /* RESISTANCE_H_ */
Program Output
100 ohms and 470 ohms in parallel: 82.46 ohms
100 ohms, 2k2 and 4k7 in series: 7000.00
220 ohms and 6k8 in series: 7020.00
● 109
C Programming with Arduino
In main.c the printf() statements have been split after the comma so that part of
the statement is on the next line in order to fit them on the printed page. The part of
each statement that appears on the next line is tabbed to the right for better looking
formatting.
In the resistance.h file Atmel Studio automatically puts some preprocessor directives in
the file when it is created in the project. These preprocessor directives prevent the header
file from being included in a C file more than once. At this stage of the book and your
programming knowledge, you can either delete these preprocessor directives from the
header file or make sure that all code added to the header file is placed between them as
shown in the solution above. More details on these preprocessor directives are presented
in chapter 16, section 16.9 on page 296.
5.15 • Summary
•
Functions can be used to break a large program into smaller units.
•
Functions can reduce the size of a program by reusing code that is called often.
•
Every function must have a function prototype, except for main().
•
Data can be passed to a function and received back from a function.
•
The type of data sent to or received from a function must be specified.
•
Preprocessor directives are read by and applied by the preprocessor before
compilation.
•
The #include preprocessor directive is used to include a header file in a
C source file. The header file can contain function prototypes and other
information.
•
The #define preprocessor directive can be used to label a constant value. The
constant value will be substituted in place of the label throughout the source
file by the preprocessor.
•
A C program can consist of more than one source file. This is made possible by
splitting the program into functions.
•
A multiple source file program is created by adding new source files in Atmel
Studio.
•
Functions can be supplied to a programmer in a library file with corresponding
header files. The programmer does not necessarily have to have access to the
source files of the functions when supplied in this way. This is the case with the
C standard library supplied with the C compiler and containing functions such
as printf() and scanf().
The main objective in this chapter was to teach you how to write your own functions
which is a big part of learning the C language. In the next chapter we look at number
systems, including the binary numbering which is an essential part of programming
embedded systems.
● 110
Chapter 6 • Number Systems
Chapter 6 • Number Systems
What you will do in this chapter:
•
Learn why we use binary numbers
•
See how the binary numbering system works
•
Learn to read and use hexadecimal numbers
•
Use hexadecimal numbers in C programs
•
The ASCII Alphanumeric code
Everything in a computer or microcontroller is stored as a binary number, whether it is
text, a program, a photograph or video. A computer only works with binary numbers. It is
therefore important to know how number systems work and this chapter will explain what
you need to know about numbers as an embedded C programmer.
6.1 • Binary Basics
Here is an example of a binary number:
11001000
This number is said to be an 8 bit number as it consists of 8 digits or bits. Each binary
digit is either a zero (0) or a one (1). The digit to the very right of the number is known
as the least significant bit (LSB) or least significant digit (LSD). The digit to the very left
of the number is known as the most significant bit (MSB) or most significant digit (MSD).
This is always the case, no matter how many bits the number consists of.
The word "bit" comes from the words binary digit.
When memory is referred to in relation to computers, it is usually specified in bytes e.g.
16 kilo bytes. A byte consists of 8 bits. A nibble is half a byte or 4 bits. The length of a
word is dependent on the system architecture to which it applies and may be 8, 16 or 32
bits for example. A word with a length of 32 bits is referred to as a 32-bit word.
1
1
1
1
bit
=
nibble =
byte =
word =
a single binary digit and can have a value of 0 or 1
a sequence of 4 bits
a sequence of 8 bits
depends on data bus width of processor
When a binary number is written in text, it is usually terminated with the letter ‘B’ to
distinguish it from a decimal number or a number from a different numbering system.
Here are some examples:
1
1
1
1
bit
nibble
byte
16 bit word
1B
1010B
00101111B
1000100100001101B
Some texts may use a lower case b at the end of a binary number e.g. 1001b. Others
● 111
C Programming with Arduino
may use a small 2 e.g. 111001012 where the 2 represents the two digit values (0 and 1)
of the binary system.
6.2 • The Need for Binary Numbers
Numbers represented on a computer consist of voltage levels. Only two voltage levels are
used: zero volts (0V) and a positive voltage e.g. five volts (5V). Binary numbers consist
of only two digits zero (0) and one (1). A binary digit 0 is stored as a voltage level of 0
volts and a binary digit 1 is stored as a positive voltage, 5V for example.
If you could open up a memory chip that is attached to a computer system and could use
a voltmeter (or multi-meter set to voltage) to measure each binary digit in the chip, you
could write out what was stored in the chip. Every time you measured 5V you would write
down a 1 and every time that you measured 0V you would write down a 0.
So a series of measurements as follows: 5V, 0V, 0V, 5V, 0V, 0V, 0V, 5V
would represent the binary number:
10010001
The actual voltage used to represent a binary digit 1 is dependent on the system that
it is used in. In the past, digital systems always used 5V to represent the binary digit
1. Many modern microprocessors run on lower voltages like 1.8V. On these systems
a binary 1 would be represented by 1.8V. Memory interfaced to one of these systems
would then have to consist of 1.8V memory chips. AVR microcontrollers on the Arduino
Uno and MEGA boards are capable of running at voltages below 5V, but the power supply
on the these Arduino boards is set to 5V. On these Arduinos a binary digit 1 would be
represented by 5V.
6.3 • Numbering Systems
6.3.1 • A Quick Look at Decimal Numbers
To help understand how binary numbers work, we will have a quick review of the decimal
numbering system.
The decimal number system should be familiar to you. Decimal numbers consist of ten
digits 0 to 9 (1 to 9 = 9 digits, 0 to 9 = 10 digits). The decimal system is therefore said
to have a base of ten. The use of only ten digits does not prevent us from counting
higher than nine. When counting to a value larger than 9, we use two or more digits.
The position of each digit in the number tells us what size the digit is. This is known as
a weighted numbering system, for example the number 44 contains the same two digits
(symbols), but their position in the number determines the value of each digit.
The weight of each digit is a power of 10 increasing from right to left, with the first digit
on the right being 1. For the example of the number 44, the first digit on the right has a
weight of 1 and the second digit from the right has a weight of 10:
4 × 10 +
4×1
= 44 (4 10s and 4 1s)
The "power of ten" is a mathematical term and is written mathematically as:
10° = 1
(ten to the power of zero equals one)
10¹ = 10
(ten to the power of one equals ten)
10² = 100
(ten to the power of two equals one hundred)
● 112
Chapter 6 • Number Systems
10³ = 1000
(ten to the power of three equals one thousand)
And so on...
In mathematics any number raised to the power of zero equals one, that is why 10° = 1.
Any number raised to the power of one is equal to itself: 10¹ = 10. The number that ten
is raised to in the above examples is the number of times that ten is multiplied by itself:
10° = 1 (any number raised to the power of 0 equals 1)
10¹ = 10 (any number raised to the power of one equals itself)
102 = 10 × 10 = 100
103 = 10 × 10 × 10 = 1000
The number 932 consists of 9 100s (10²), 3 10s (10¹) and 2 1s (10°):
9 × 100 +
3 × 10 +
2×1
= 932
The number 8157 can be viewed as follows:
Decimal numbers are written without anything appended to them to show which
numbering system they are from e.g. 100 and 95 are decimal numbers whereas 100B
represents a binary number. Decimal numbers are sometimes written with their base
number included e.g. 19110 (one hundred and ninety one base ten) where 10 represents
the ten digits that make up the decimal number system.
6.3.2 • Binary Numbers
The binary number system uses only two digits as already explained (base two) and is
also a weighted number system. When counting in decimal starting from 0, we move
through the ten digits of the decimal system. After 9 is reached, the place holder to the
left of the 9 is increased from 0 to 1 and the number becomes 10. Counting continues at
11 and when 19 is reached, the left most digit is incremented again so that the number
becomes 20. This continues until 99 is reached and then the place holder to the left of
this number is increased to 1 making the number 100.
Binary works in the same way, but there are only two digits to count through until the
place holder to the left of the number must be increased by 1. We start with a count of 0
and count to 1. We are now out of digits so need to increment the place holder to the left
of the number by 1. So the binary number becomes 10 (one zero). The digit to the right
is now incremented and the number becomes 11 (one one). This would be equivalent of
running out of digits when 99 is reached in decimal. In decimal the count changes to 100
and in binary the same happens. The number now becomes 100, but the actual value
is not read as "one hundred" when using binary it is read as "one zero zero" which has
a count value of 4. After 100 (one zero zero), the count continues to 101, 110 and 111.
This may seem strange at first as it is a new concept, it is not difficult to understand but
will require some getting used to.
You can use the Windows Calculator program to experiment with binary numbers as
shown in Figure 6-1 on page 114. To start Windows Calculator click the Windows start
● 113
C Programming with Arduino
button, type calculator and hit Enter.
The first thing that you will need to do before using the calculator to work with binary
numbers is to change it to programmer mode. In the calculator program click the View
menu and then Programmer. Now click the Bin radio button, which is displayed in the
calculator window when it is in programmer mode, to put the calculator into binary mode.
We will use the calculator to count in binary, starting at 1 and adding 1 each time. Click
the "1" button and then the "+" button. Now click the "=" button to start counting. Just
keep clicking it to count up by 1 each time.
Figure 6-1 Windows Calculator Used for Counting in Binary
Any time that you would like to see what the equivalent decimal number is of your
current binary number, just click the Dec radio button. Click the Bin radio button to
change back to binary, you can then continue to click the "=" button to carry on counting.
We have seen how decimal numbers are weighted. Binary numbers are weighted as
follows:
20 = 1
21 = 2
22 = 4 (2 × 2)
23 = 8 (2 × 2 × 2)
etc.
The number 1110 when read from right to left consists of:
0×1+
1×2+
1×4+
1×8
=8+4+2+0
= 14
Another example, the number 10100011
● 114
Chapter 6 • Number Systems
1×1+
1×2+
0×4+
0×8+
0 × 16 +
1 × 32 +
0 × 64 +
1 × 128
= 128 + 0 + 32 + 0 + 0 + 0 + 2 + 1
= 163
You can use Windows Calculator to convert these binary numbers to decimal. First select
binary mode using the Bin radio button, type the number into the calculator and then
change to decimal mode using the Dec radio button. The number will be converted when
changing modes.
Table 6-1 shows a binary count of 0 to 15.
Table 6-1 Decimal Numbers with Binary Equivalents
Decimal
Binary
0
0000
1
0001
2
0010
3
0011
4
0100
5
0101
6
0110
7
0111
8
1000
9
1001
10
1010
11
1011
12
1100
13
1101
14
1110
15
1111
An easy way to learn how to write a sequence of binary numbers as seen in the table is
to start with the right most bit (LSB) and write down a sequence of alternating numbers
vertically starting from 0 i.e. 01010101... The next number to the left of this will consist
of a sequence of numbers also starting from 0, but changing every two numbers i.e.
001100110011… To the left of this number, sequences of four numbers starting from 0
i.e. 0000111100001111… To the left of this number, sequences of eight numbers starting
from 0 i.e. 0000000011111111… Figure 6-2 on page 116 will make this clearer.
● 115
C Programming with Arduino
Figure 6-2 An easy way to count in Binary
Why we need to know about binary numbers
Embedded system programming consists of communicating with hardware devices
attached to the microcontroller as well as hardware devices that are found on the
microcontroller chip itself. These hardware devices are set up and operated by writing
numbers to them and reading numbers back from them. Take the example of a
microcontroller interfaced to four LEDs as shown in Figure 6-3 on page 117.
To switch on LEDs D1 and D4 we write the binary number 1001 to the LEDs. Because
binary numbers are stored as voltage levels, we are actually connecting 5V to the anode
of D1, 0V to the anode of D2, 0V to the anode of D3 and 5V to the anode of D4. When
we write 1001 to the LEDs D1 and D4 will therefore switch on and LEDs D2 and D3 will
switch off.
We will see more examples of writing binary numbers to switch things on and off in the
practical embedded programming chapters of this book. In C programs we actually use
hexadecimal numbers instead of binary numbers, but hexadecimal numbers are a direct
representation of binary numbers. You could also use decimal numbers in C as sending
the number 9 to the LEDs will send the correct binary number of 1001, however it is
easier to see what is being switched on or off when looking at the binary number. One
can immediately see from the binary number that the first bit is switching something on,
the middle two bits are switching something off and the last bit is switching something
on.
6.3.3 • Hexadecimal Numbers
The hexadecimal number system is a shorter way to represent binary numbers. We can
use hexadecimal numbers directly in our C programs. Hexadecimal numbers make binary
numbers easier to read.
We have seen the base 10 decimal system and the base 2 binary system. The
hexadecimal system is base 16 (hex = 6, decimal = 10, hexadecimal = 6 + 10 = 16
digits). When counting in decimal, we use ten digits 0 to 9. In the hexadecimal system
● 116
Chapter 6 • Number Systems
we have an extra 6 numbers to count to and use the letters A to F to represent them. So
counting up in hexadecimal using a single digit will look like this:
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F
Figure 6-3 LEDs Interfaced to a Microcontroller
If we want to count higher than F, we are now out of digits and have to increment the
place holder to the left of the single digit number, so the counting sequence continues as
follows:
10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 1A, 1B, 1C, 1D, 1E, 1F, 20, 21,
22, 23, 24, 25, 26, 27, 28, 29, 2A, 2B ...
Hexadecimal numbers are terminated with an H when written in text or with a 16 for
example:
19A34C0H
or
19A34C016
In C source code hexadecimal numbers are written starting with 0x (zero ex) as follows:
0x19A3C0
Each hexadecimal digit directly represents a four bit binary number. If you want to know
how high you can count to using a binary number of x length, use the formula:
max_count = 2x - 1 (maximum count = two to the power of x minus one) where x is the
number of bits in the binary number.
If we want to know how high it is possible to count with just 2 bits, we calculate it as
follows:
max_count=
=
=
=
=
2x – 1
22 – 1
2 × 2 - 1
4 – 1
3
This means that it is possible to count to 3 using two binary digits. Let’s check this:
● 117
C Programming with Arduino
00B = 0
01B = 1
10B = 2
11B = 3
With Windows Calculator in decimal mode, you can do this calculation more easily. Start
the calculator and put it into scientific mode by clicking View → Scientific. Now Press
2, then press the xy button, press 2 again and then =. You have now calculated 22. Now
subtract 1: press - and then 1 and finally =.
An 8 bit number can be used to count up to:
max_count
=
=
=
=
=
2x – 1
28 – 1
2 × 2 × 2 × 2 × 2 × 2 × 2 × 2 - 1
256 – 1
255
It is possible to count up to 255 with an 8 bit number.
To do this calculation in Windows Calculator in scientific mode, press 2, xy, 8, -, 1, =
Now let’s look at a 4 bit number:
max_count
=
=
=
=
=
2x – 1
24 – 1
2 × 2 × 2 × 2 - 1
16 – 1
15
In Windows Calculator, press: 2, xy, 4, -, 1, =
15 is the same number that a single hexadecimal number can count up to (F hex = 15
dec). Note that there are 16 hexadecimal digits, but the first digit is zero. Counting from
1 to F in hexadecimal is the same as counting from 1 to 15 in decimal or 1 to 1111 in
binary (Table 6-2). You can use Windows Calculator in programmer mode to convert
between numbers from different number systems. Switch the calculator into whichever
number system you want to convert from by using one of the radio buttons: Hex for
hexadecimal, Dec for decimal or Bin for binary. Type the number in that you want to
convert and then using the radio buttons again, select the number system that you would
like to convert to.
To count up in hexadecimal, put the calculator into hexadecimal mode. Type "1" and then
"+" and then continually press the "=" key to count up.
Table 6-2 Counting in Decimal, Hexadecimal and Binary
Decimal
Hex
Binary
0
0
0000
1
1
0001
2
2
0010
3
3
0011
4
4
0100
5
5
0101
6
6
0110
7
7
0111
8
8
1000
● 118
Chapter 6 • Number Systems
Decimal
Hex
Binary
9
9
1001
10
A
1010
11
B
1011
12
C
1100
13
D
1101
14
E
1110
15
F
1111
Why we need the hexadecimal number system in C programming
Hexadecimal numbers are needed so that we can switch external hardware devices on
and off just like with the binary example. You can not type a binary number directly into
your C source code to use it in your program. You could write a function to do this, but an
easier way is to use a hexadecimal number. Hexadecimal numbers are also shorter than
binary when typed. For example the 32 bit binary number:
10000101111100001111000010101100
is written in hexadecimal as:
0x85F0F0AC
How to use hexadecimal numbers in C
If you had a microcontroller with a 32 bit port connected to 32 LEDs, you could write the
above number to the port to switch the LEDs on and off. Let’s take a simpler example of
an 8-bit port with 8 LEDs interfaced to it. Let’s switch the first two LEDs on, the next two
off, the next 3 off and the last one on (2 on, 2 off, 3 off, 1 on). Notice firstly that I am
visualising the LEDs in groups of four (groups of 1 hexadecimal digit each) that is why I
have not written 2 on, 5 off, 1 on). The binary number to switch this sequence will be:
1100 0001
Now convert this binary number to hexadecimal to use it in a C program. You can just
look up the number in table 6.3 or type it into Windows Calculator and convert it. When
you have worked with binary and hexadecimal numbers for a long time, you will be able
to convert them in your head. When using the table to look up the hexadecimal number,
group the binary number into four bits each starting from the right of the number and
look up each four bit number in the table.
0001B is 1H
1100B is CH
The number will therefore be:
C1H
or
0xC1 when written in a C program.
How to convert from binary to hexadecimal in your head
You might want to come back to this method for converting binary numbers to
hexadecimal in your head later after you have worked with binary and hexadecimal
numbers for a while.
● 119
C Programming with Arduino
Always work in groups of four digits, starting from the right of the binary number. Each
bit is weighted as follows:
Note that when numbering bits, they are numbered from right to left starting at 0.
Now apply the weighting to each group of four bits by multiplying the weight by the
binary digit under it. Add the results of this operation together, and then count in hex to
convert. A few examples will clarify.
To convert the 8-bit binary number 1100 0001 to hexadecimal:
Taking the lower nibble (first four bits from the right: 0001) line it up under the weight
values.
Now multiply each bit by its weight and then add the results:
8 × 0 + 4 × 0 + 2 × 0 + 1 × 1 = 1
Now the upper nibble (left four bits of the byte):
8 × 1 + 4 × 1 + 2 × 0 + 1 × 0 = 12
The number 12 is in decimal, so we need to count through this number to convert it to
hexadecimal. Start counting 1, 2, 3, 4, 5, 6, 7, 8, 9, we can’t count ten we must count
A, B, C that’s 12 counts so 12 dec = C hex. Now put the two numbers together in the
correct order and you get: 0xC1
Another example using the 32 bit number shown earlier:
10000101111100001111000010101100
First break it up into groups of 4:
1000 0101 1111 0000 1111 0000 1010 1100
Now convert each group of four. You may convert the groups from left to right, so long
as you have broken them up from right to left. This is because if you have say, a 7 bit
number like 1010111, it is broken up as 101 0111 and not 1010 111.
Each group will be converted from left to right for this example.
1000 = 1 × 8 + 0 × 4 + 0 × 2 + 0 ×
1 = 8
No need to count because 8 dec = 8 hex
0101 = 0 × 8 + 1 × 4 + 0 × 2 + 1 × 1 = 5
5 dec = 5 hex
Start assembling the number: 0x85
1111 = 1 × 8 + 1 × 4 + 1 × 2 + 1 × 1 = 15
Start counting to 15 by counting through the hex number sequence. After 9, count A, B,
C, D, E, F. You will quickly learn to recognise that when all 4 bits of the number are 1s,
the biggest hex digit is used i.e. F.
The number is now 0x85F
● 120
Chapter 6 • Number Systems
0000 = 0 × 8 + 0 × 4 + 0 × 2 + 0 × 1 = 0
0 again
The number is now 0x85F0, we are half way there…
1111 = 1 × 8 + 1 × 4 + 1 × 2 + 1 × 1 = 15
F again.
The number is now 0x85F0F
0000 = 0 × 8 + 0 × 4 + 0 × 2 +0 × 1 = 0
0 again.
The number is now 0x85F0F0
1010 = 1 × 8 + 0 × 4 +1 × 2 + 0 × 1 = 10
Start counting to ten in hex. After 9 comes ten, but in hex it is A. The number is now
0x85F0F0A. Last number coming up…
1100 = 1 × 8 + 1 × 4 + 0 × 2 + 0 × 1 = 12
Count to 12 in hex 1…9, A, B, C. The number is 0x85F0F0AC.
Let’s look at one final example, but this time simplifying things a bit. We will line up our
8421 weighting above each 4 bit section of the number in our minds and whenever there
is a 1 in the number, we will write the weight down. Whenever there is a 0 in the number,
we will write 0 down. Our example number is:
11011110
Split it up into fours:
1101 1110
Now do the conversion:
1101 = 8 + 4 + 0 + 1 = 13
Count up to 13 in hex: 1…9, A, B, C, D.
1110 = 8 + 4 + 2 + 0 = 14
Count up to 14 in hex: 1…9, A, B, C, D, E. The number is therefore: 0xDE
6.4 • Working with Hexadecimal Numbers in C
Now that we have seen how hexadecimal and binary numbers work, let’s see how to use
them in some C programs. You won’t be able to use a binary number directly in your C
program, but will use the hex equivalent instead.
The printhex program demonstrates how to print a hexadecimal number:
Project name: printhex
Project location: CH6\printhex
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
● 121
C Programming with Arduino
UartInit();
printf("%x", 0xA914);
printf("\r\n0x%x", 0xA914);
printf("\r\n0x%X", 0xA914);
}
// print a hex number
// print same number with 0x
// hex number in capitals
while(1) {
}
Program Output
a914
0xa914
0xA914
The example program shows that to write a hexadecimal number in C, it is always started
with 0x. When printing a number in hexadecimal using printf(), the %x format specifier
is used. The second time that the number is printed out, 0x is included in the text string
so that the hexadecimal number is displayed as 0xa914 rather than a914. The third
time that the hexadecimal number is printed out, the format specifier uses a capital x
(%X) which prints the hexadecimal number using capital letters for the numbers A to F:
0xA914 instead of 0xa914.
Numbers in a computer or microcontroller are always stored as binary numbers. When
we print out a number in decimal or use a decimal number in our program, we are only
displaying the number on the screen as a decimal number. We can view the same number
as a hexadecimal number. When typing a decimal or hexadecimal number into a source
code file, we are using a number that is easy for people to read. After compiling the
source code and then loading the program to memory to run it, all of the numbers from
the source code are converted to and stored as binary numbers. The next program called
printnum demonstrates that we can view the same number stored in a variable as either
decimal or hexadecimal.
Project name: printnum
Project location: CH6\printnum
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
int the_num;
UartInit();
the_num = 498;
printf("The number is %d in dec\r\n", the_num);
printf("The number is %X in hex\r\n", the_num);
// change the number
the_num = 0x3A9C;
printf("\r\nThe number is %d in dec\r\n", the_num);
printf("The number is %X in hex\r\n", the_num);
}
while(1) {
}
● 122
Chapter 6 • Number Systems
Program Output
The number is 498 in dec
The number is 1F2 in hex
The number is 15004 in dec
The number is 3A9C in hex
When the variable the_num in this program is assigned a value of 498, it is actually being
assigned the binary equivalent of the decimal number 498 which is 111110010B (check
this on your Windows Calculator). In reality, a location in memory is set aside for this
variable and after assigning it this number, it contains the voltage levels:
5V, 5V, 5V, 5V, 5V, 0V, 0V, 5V, 0V
Where 5V represents whatever voltage level is used on the system for the binary value 1.
Whether we choose to display the number as hexadecimal or decimal in our source code
or screen makes no difference, the number is always stored as a binary number on the
microcontroller, consisting of voltage levels.
And now for a program that converts hexadecimal to decimal and vice versa, convnum.
Project name: convnum
Project location: CH6\convnum
main.c
#include <stdio.h>
#include "stdio_setup.h"
void menu(void);
int main(void)
{
char menu_item = 0;
int num;
UartInit();
}
while(1) {
menu();
// display the menu
scanf("%c", &menu_item);
// get key press
if (menu_item == 'h') {
printf("\r\nEnter hex num: ");
scanf("%x", &num);
printf("\r\nNumber in dec is %d\r\n", num);
scanf("%c", &menu_item);
// remove incoming carriage return
}
else if (menu_item == 'd') {
printf("\r\nEnter dec num: ");
scanf("%d", &num);
printf("\r\nNumber in hex is %X\r\n", num);
scanf("%c", &menu_item);
}
else {
printf("\r\nInvalid menu selection...\r\n");
}
}
void menu(void)
{
● 123
C Programming with Arduino
}
printf("\r\nPress h to convert hex to dec.");
printf("\r\nPress d to convert dec to hex. ");
Example Program Output
Press h to covert hex to dec.
Press d to convert dec to hex. h
Enter hex number: 2f
Number in dec is 47
Press h to covert hex to dec.
Press d to convert dec to hex. d
Enter dec num: 478
Number in hex is 1DE
A function is used for displaying a menu to the user in this program. The program waits
for the user to select one of the menu items and then, depending on the user’s selection,
will either prompt the user to convert a number from hexadecimal to decimal or decimal
to hexadecimal. When converting the hexadecimal number to decimal, the number can be
entered in upper or lowercase, with or without a preceding 0x e.g. 1de, 1DE, 0x1DE. As in
the previous program, the number stored in a variable is printed in either hexadecimal or
decimal to the screen.
The size of the number that can be entered is limited by the size of an integer (of type
int) on the system that the program is being run on. In the AVR 8-bit GCC compiler, an
integer is 16 bits wide, limiting the decimal number that can be entered to 65535
(216 - 1 = 65535) which will convert to 0xFFFF. Any number greater than 65535 will have
its upper digits truncated. When converting from hexadecimal to decimal, numbers higher
than 0x7FFF will display as negative numbers. This is because an int is a signed number,
meaning that it can store a positive or negative number. The number will become
negative when the most significant digit of the int is set to 1. More on this later.
6.5 • The ASCII Alphanumeric Code
When working with character data types (char) in C programs, you are working with
ASCII characters. What does this mean?
As already mentioned, computers and microcontrollers only work with numbers. When
sending text from one microcontroller to another or writing and saving text on a
computer, only numbers can be used. When a text file is opened, the computer knows
that the file is a text file as it has a .txt file extension or other file extension that lets the
computer know that the file contains text. When the computer reads the contents of the
text file and displays it on the screen, it will read a byte at a time and will interpret each
byte. If it were to read the number 0x41 (01000001B or 65), it will interpret this and
display the letter ‘A’ on the screen. The computer uses the ASCII code to look up this
number and display the correct letter or symbol.
ASCII stands for "American Standard Code for Information Interchange". The ASCII code
consists of letters from the alphabet, numbers 0 to 9 and other characters like brackets
and punctuation marks. When the computer reads ASCII text, it will look up the numbers
that the text represents in the ASCII code and display the corresponding letter or symbol.
The original ASCII table consisted of 7-bit numbers (0 to 127), but it has been extended
to 8 bit numbers (0 to 255) to support languages other than English as well as add some
graphical symbols. See Appendix A on page 329 for the extended ASCII table.
● 124
Chapter 6 • Number Systems
The next program called asciishow displays a character as ASCII text, hexadecimal and
decimal. Refer to Appendix A on page 329 to see the characters used in this program
and their numeric values.
Project name: asciishow
Project location: CH6\asciishow
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
char chr;
UartInit();
chr = 'T';
printf("Character:%c Hex: 0x%x Dec: %d\r\n", chr, chr, chr);
chr = 'i';
printf("Character:%c Hex: 0x%x Dec: %d\r\n", chr, chr, chr);
chr = '8';
printf("Character:%c Hex: 0x%x Dec: %d\r\n", chr, chr, chr);
}
while(1) {
}
Program Output
Character: T Hex: 0x54 Dec: 84
Character: i Hex: 0x69 Dec: 105
Character: 8 Hex: 0x38 Dec: 56
The program prints out the value that the variable chr is holding three times in each
printf() statement by specifying chr as the variable to print three times. The variable
is first printed out as a character, then as a hexadecimal number and finally as a decimal
number.
The chr variable is first initialised with the character ‘T’. ‘T’ can be found in the ASCII
table and has a value of 0x54 in hexadecimal or 84 in decimal. In the printf()
statements, the value that chr is holding is printed as a character using the %c format
specifier, then as a hexadecimal number using the %x format specifier and finally as a
decimal number using the %d format specifier. The contents of variable chr remains the
same throughout each printing. The reason that it is displayed differently is because
it is first interpreted as an ASCII character, then as a number in two different number
formats.
When the variable chr is assigned a value of ‘i’, it prints out as 0x69 or 105 as a number.
Again, these numbers can be found in the ASCII table as the number representing the
ASCII character ‘i’.
Finally chr is assigned the value ‘8’ which is the ASCII character ‘8’ and not the number
8. Numbers are also encoded in the ASCII table, so looking up the ASCII character ‘8’
shows that it has the numerical value of 0x38 or 56.
● 125
C Programming with Arduino
From this chapter I hope that you understand that:
1.
Numbers are stored as voltage levels on a computer:
0V, 5V, 0V, 0V, 0V, 0V, 5V, 5V
Note that when copying data from magnetic or optical media to memory, data
is changed from being stored as magnetic fields or pits and lands to voltage
levels. A microcontroller can only work with voltage levels, so all data that is
to be processed, no matter where it is stored, is first changed to voltage levels
and later if saved to other media, changed back to magnetic fields or however
it is stored on the particular media.
2.
These voltage levels are known as "logic levels" and represented as binary
numbers:
0100 0011
3.
These same logic levels or binary numbers can be viewed by people or written
in C programs as decimal numbers:
67
Or they can be viewed as hexadecimal numbers:
0x43
Or they can be viewed as characters:
C
So whether you type ‘C’, 67 or 0x43 into your C source file, you are essentially
typing the same binary number of 01000011 and can choose in which number
system to display it on the screen when your program prints it out. Note that
this is not specific to the C language, but to computers and digital systems in
general.
6.6 • Exercises
6.6.1 • Hexadecimal to Port
A microcontroller is interfaced to an 8 bit port that is driving 8 LEDs. What hexadecimal
number would you write to this port to switch the first two LEDs on (left most LEDs), the
second two LEDs off, the next 3 LEDs on and the last (right most) LED off?
6.6.2 • Print Number
Write a program to print the hexadecimal number 0x5f as a character, as a decimal
number and as a hexadecimal number.
6.7 • Solutions
6.7.1 • Solution to 6.6.1
The LEDs will be On, On, Off, Off, On, On, On, Off. This equals: 1100 1110B
The hexadecimal equivalent will be:
● 126
Chapter 6 • Number Systems
8 + 4 for the first hex digit on the left (left four bits)
= a count of 12
= count from 10 to 12 in letters starting from A
=C
8 + 4 + 2 for the hex digit on the right (right four bits)
= a count of 14
= count from 10 to 14 in letters starting from A
=E
Put the two together and the answer is: 0xCE
6.7.2 • Solution to 6.6.2
printf("%c\t%d\t%X", 0x5F, 0x5F, 0x5F);
Project name: CH6_1_print_number
Project location: Exercises\CH6_1_print_number
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
UartInit();
printf("%c\t%d\t%X", 0x5F, 0x5F, 0x5F);
while(1) {
}
}
Program Output
_
95
5F
In this solution the Tab escape sequence \t is used to separate each value printed to the
terminal window. The character printed is the underscore and can be found in the ASCII
table in appendix A.
6.8 • Summary
•
Numbers are stored as voltage levels in a computer or microcontroller’s
memory.
•
These voltage levels are represented in binary as zeros (0s) and ones (1s).
•
Writing a number to an external port on a microcontroller can switch devices
that are attached to the port on or off.
•
Reading a number from an external port will get the state of external devices
that are attached to the port. The state of these devices will be reflected in the
● 127
C Programming with Arduino
number read from the port.
● 128
•
Binary numbers can be represented conveniently and in a more compact format
by hexadecimal numbers.
•
In the C programming language, hexadecimal numbers can be used directly to
represent binary numbers and are written starting with 0x.
•
The ASCII alphanumeric code is used to interpret numbers as characters.
•
Any number in a computer or microcontroller’s memory can be represented as
a binary number, a hexadecimal number and a decimal number. If the number
is read as an 8-bit number it can be represented as an ASCII character.
Chapter 7 • Memory and Microcontrollers
Chapter 7 • Memory and Microcontrollers
What you will do in this chapter:
•
Find out how memory works
•
Learn how a microprocessor accesses memory and peripherals
•
See how to access memory by using the address of the memory
•
Learn about other data types in C
7.1 • Memory Basics
Two main types of memory are present on microcontrollers and embedded systems,
namely ROM and RAM. These memory types are used for storing program instructions
from a compiled C program and data variables that are defined in a C program.
7.1.1 • ROM (Read Only Memory)
This type of memory is used to store the program that is written and compiled. It is nonvolatile memory meaning that it will retain its contents even if the power is switched
off. ROM on a modern microcontroller is usually present as Flash memory as it is on the
Arduino AVR microcontrollers. The contents of the Flash memory are normally not altered
when a user application program is running, although some microcontrollers’ Flash
memory is in-application programmable. The program stored in ROM memory will be the
program that is run when power is supplied to the microcontroller.
7.1.2 • RAM (Random Access Memory)
RAM is used to store variables and data from our program that can be changed while the
program is running. This type of memory is volatile meaning the contents of the chip will
be lost when power is removed. Microcontrollers usually contain some SRAM (Static RAM)
as the AVR microcontrollers do.
Figure 7-1 Memory Showing How Data is Stored
7.1.3 • Data Stored in Memory
Data is stored in memory as an array of bytes. Each byte has a unique address and can
be individually addressed. Data is stored to memory by a write operation and retrieved
from memory by a read operation. Each bit in a byte of a memory chip is called a cell and
can hold either a 1 or a 0 logic level as shown in Figure 7-1. The capacity of the memory
● 129
C Programming with Arduino
chip is determined by how many addressable bytes it contains.
7.2 • A Look at a Memory Chip
Let’s take a look at how a SRAM memory chip works in order to better understand
memory. Figure 7-2 shows the top view of a 2kB (two kilo byte) memory chip.
Figure 7-3 on page 131 shows a block diagram of the same chip. The size of the
memory is determined by how many bytes it can address. This is determined by the
number of address lines on the chip. The chip has 11 address lines and so can address 211
bytes = 2048 bytes (or 2 kilo bytes because 1 kilo byte = 1024 bytes or 210).
Figure 7-2 An 8-Bit SRAM Memory Chip
A bus is just a group of wires on a computer system. An address bus is a group of wires
that are used for addressing memory and peripheral devices and is 11 bits wide on the
memory chip shown. A data bus is the group of wires used for sending and receiving data
to and from memory and peripheral devices. The data bus on the memory chip in the
figure is 8 bits wide. A control bus is the group of wires used to control a memory chip
or peripheral device. There are 3 control lines on the memory chip. A peripheral device is
any piece of hardware interfaced to the microcontroller, whether it is an internal device or
external to the microcontroller.
● 130
Chapter 7 • Memory and Microcontrollers
Figure 7-3 Block Diagram of an SRAM Memory Chip
The procedure for writing data to or reading data from a memory chip is described below.
If you plugged this memory chip into an electronic breadboard and used wires to connect
the chip as described, you could write data to the chip and read data from the chip by
plugging the correct sequences of wires to the correct power supply terminals and reading
the data by measuring voltage levels with a voltmeter.
In order to operate the memory chip of Figure 7-2 on page 130, power must first be
supplied to the chip on the VDD and GND pins – the positive voltage specified in the
chip’s datasheet is supplied to VDD from a power supply and the 0V of the power supply
is connected to the GND pin. The three control lines of the control bus, CS, OE and WE
must be "pulled high" i.e. they must have a logic 1 applied to them – connected to the
positive of the power supply. This is because they are "active low" meaning that they
are active when logic 0 is applied to them. We know this because of the line above
these names as shown in the figure. If there was no line, they would be "active high"
– activated by a logic 1 level. The chip will now be in an inactive state and the voltage
levels measured on the address and data bus will be "high impedance" meaning that
they are neither at logic 0 nor at logic 1, but are offering a high resistance – more like an
insulator than a conductor.
From this inactive state, we can write something to the chip. First we decide at which
address we would like to save the data byte that we are going to write. If we choose the
first address in the chip, address 0, then we must supply the address 000 0000 0000B to
the address bus. In other words we must connect the 11 address lines of the address bus
(A0 to A10) to 0V of the chip’s power supply. Remember that we are counting from 0 so
A0 to A10 are 11 lines, not 10.
Next we must decide what data byte to write. Let’s say 120 decimal. This is the
equivalent of 78 in hexadecimal which is 0111 1000 in binary. We must therefore connect
D7 of the data bus to 0V of the power supply, D6 to D3 to the positive of the power
supply and D2 to D0 to the 0V of the power supply.
The address is now addressing the first data byte in the memory chip, the data bus has
the data on it that we would like to save to the chip, we must now operate the control
signals to move the data into the chip. The chip must first be selected by putting logic
0 on the CS pin i.e. connect it to the 0V of the power supply. Next we put logic 0 on the
WE pin which moves our data byte into the chip and saves it at address 0. The CS and
WE pins can now be moved back to logic 1 to disable the chip again. The chip will now
keep our data byte stored at address 0 until we change it by writing a byte to the same
address or the power is switched off. This whole operation is known as a write operation.
To read our data byte back from the chip we must first apply the address to the chip’s
address bus again. No voltage levels must be applied to the data bus or they may be
shorted out when the read is performed. Next, the chip is enabled with the CS pin pulled
to logic 0. The output enable (OE) pin is now pulled to logic 0 and the byte that we saved
at address 0 will now appear as binary voltage levels on the data bus. You would be able
● 131
C Programming with Arduino
to measure them with a voltmeter.
This same process can be applied to any data byte in the chip by changing the address
on the address bus. Want to read or write byte number 1045? Apply the address 1045
to the address bus which is 0x415 or 100 0001 0101B and then follow the read or write
procedure.
Table 7-1 shows the write and Table 7-2 shows the read of the memory chip graphically.
Read each table from top to bottom where the first line shows the address and data bus
in high impedance indicated by a Z at each bit, and all control signals are in their inactive
state with logic 1 levels applied.
Table 7-1 Writing 0x78 to a Memory Chip at Address 0
Address Bus
Data Bus
A
7
A
6
A
5
A
4
A
3
Control
Bus
A
10
A
9
A
8
A
7
A
6
A
5
A
4
A
3
A
2
A
1
A
0
A
2
A
1
A
0
C
S
W
E
O
E
Z
Z
Z
Z
Z
Z
Z
Z
Z
Z
Z
Z
Z
Z
Z
Z
Z
Z
Z
1
1
1
0
0
0
0
0
0
0
0
0
0
0
Z
Z
Z
Z
Z
Z
Z
Z
1
1
1
0
0
0
0
0
0
0
0
0
0
0
0
1
1
1
1
0
0
0
1
1
1
0
0
0
0
0
0
0
0
0
0
0
0
1
1
1
1
0
0
0
0
1
1
0
0
0
0
0
0
0
0
0
0
0
0
1
1
1
1
0
0
0
0
0
1
Z
Z
Z
Z
Z
Z
Z
Z
Z
Z
Z
Z
Z
Z
Z
Z
Z
Z
Z
1
1
1
Table 7-1 rows are read from top to bottom as follows:
Steps
1.
Chip is inactive (Z = high impedance or disconnected)
2.
Address is applied to address bus
3.
Data to be written is applied to the data bus
4.
Chip select CS is enabled
5.
Activating the WE control line moves the data into the chip at the selected address
6.
Chip is put back into the inactive state
Table 7-2 Reading Data from a Memory Chip at Address 0
Address Bus
Data Bus
A
7
A
6
A
5
A
4
A
3
Control
Bus
A
10
A
9
A
8
A
7
A
6
A
5
A
4
A
3
A
2
A
1
A
0
A
2
A
1
A
0
C
S
W
E
O
E
Z
Z
Z
Z
Z
Z
Z
Z
Z
Z
Z
Z
Z
Z
Z
Z
Z
Z
Z
1
1
1
0
0
0
0
0
0
0
0
0
0
0
Z
Z
Z
Z
Z
Z
Z
Z
1
1
1
0
0
0
0
0
0
0
0
0
0
0
Z
Z
Z
Z
Z
Z
Z
Z
0
1
1
0
0
0
0
0
0
0
0
0
0
0
Z
Z
Z
Z
Z
Z
Z
Z
0
1
0
0
0
0
0
0
0
0
0
0
0
0
0
1
1
1
1
0
0
0
0
1
0
Z
Z
Z
Z
Z
Z
Z
Z
Z
Z
Z
Z
Z
Z
Z
Z
Z
Z
Z
1
1
1
● 132
Chapter 7 • Memory and Microcontrollers
Table 7-2 on page 132 rows are read from top to bottom as follows:
Steps
1.
Chip is inactive (Z = high impedance or disconnected)
2.
Address is applied to address bus
3.
Chip is selected by activating CS
4.
Output enable OE control line is activated
5.
Data being read now appears on the data bus
6.
Chip is put back into the inactive state
7.3 • How Microprocessors Access Memory and Peripherals
A microprocessor has an address bus, a data bus and a control bus. A memory chip is
interfaced to the microprocessor by connecting its buses to the microprocessors buses.
On a microcontroller with on-chip memory, this is already done inside the chip.
The microprocessor contains registers – these are similar to memory locations in the
microprocessor as they store a number. One of these registers is called the program
counter (PC) and the value in this register is placed on the microprocessor’s address bus.
When power is applied to the microprocessor, the program counter is reset to a value of
0, so at reset an address of 0 appears on the microprocessors address bus (all address
lines are at logic 0). When a non-volatile memory chip is wired to the microprocessor at
this address and it contains a program, the microprocessor will automatically fetch the
first instruction of the program from the memory chip at address 0 and execute it (an
instruction is just a binary number). The program counter will then count up by one and
fetch the instruction from the memory at address 1 and execute it. The program counter
will keep counting up and fetching each successive byte of the program from the memory
chip and execute it. If a jump instruction occurs, the address in the program to jump
to is loaded into the program counter and execution of instructions continues from the
new address with the program counter incrementing the address after each instruction is
fetched.
A microprocessor or microcontroller will "boot up" – by starting to run a program at
a known address. The start-up address does not have to be 0, but usually is. Some
microprocessor manufacturers will design their chips to start running at a different
address. A non-volatile memory chip containing the program to be run must then
be wired at this address by the hardware engineer who builds the system or by the
microcontroller manufacturer when a microcontroller is designed.
A peripheral device, like a bank of 8 LEDs, can be interfaced to the microprocessor by
using a buffer chip that is like a single memory location. The chip will hold the voltage
levels written to it and will be able to deliver enough current to drive the LEDs to switch
them on. The voltage levels that the chip is holding are available externally on the pins of
the chip and this is where the LEDs are connected. Writing to a single memory location in
a memory chip and writing to a bank of LEDs is the same operation in a microprocessor.
Writing to the memory location just saves a number, writing to the LEDs displays the
number on the LEDs in binary where an LED that is on represents binary 1 and an LED
that is off represents a binary 0. Figure 7-4 on page 134 shows a block diagram of a
microprocessor interfaced to Flash memory, SRAM memory and peripheral devices.
In C, we can write to or read from a peripheral that is at a specific address. This will
cause the microprocessor to load this address on the address bus and fetch data from
or send data to the device on the data bus. Addresses in C can be accessed by pointers
which we will look at next.
● 133
C Programming with Arduino
Figure 7-4 Microprocessor Interfaced to Memory and Peripherals
7.4 • Pointers
Pointers in C point to locations in memory or can be used to point to control registers of
a peripheral device. Pointers contain the address of the device or memory location that
they are addressing or pointing to. Before we start to use pointers, let’s write a program
that displays the address of a variable in memory. We know that a variable is a piece
of memory set aside by the compiler for use in a program. Because the variable exists
in memory, it must have an address. The address program displays the address of an
integer variable.
In this program, the value that the variable var is holding is printed first. The address of
this variable is then printed out in decimal and hexadecimal. To print the address of the
variable, we use the ampersand character (&). This should be familiar to you from using
the scanf() function where we passed the address of the variable in which we wanted to
save data entered by the user to scanf(). When using the name of a variable in C we are
referring to the value that the variable (or memory location) is storing. When preceding
the variable name with an ampersand, we are referring to the address of the variable in
memory as Figure 7-5 on page 135 shows.
Project name: address
Project location: CH7\address
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
int var = 100;
UartInit();
printf("The variable var contains the value %d\r\n", var);
printf("The address of var is %d = 0x%X", &var, &var);
}
while(1) {
}
● 134
Chapter 7 • Memory and Microcontrollers
Example Program Output for UNO
The variable var contains the value 100
The address of var is 2298 = 0x8FA
Example Program Output for MEGA
The variable var contains the value 100
The address of var is 8697 = 0x21F9
Figure 7-5 Variable Address and Variable Data in C
The actual address that a variable is located at is assigned during the build process in
Atmel Studio. A number of factors can affect the address of a variable, such as the size
of the program, optimisation settings of the compiler and the actual microcontroller
selected. In a C program it does not matter where a variable is located because we refer
to the variable by its name. A valid address will automatically be assigned to a variable
during the build process depending on the above factors. This is the reason that the
program output listed above is different for the Uno and Mega boards. These boards each
have different microcontrollers which each have different memory sizes.
For our next example, we create a pointer and make it point to a variable. If the variable
was a port with LEDs attached to it, we would then be able to control the LEDs by writing
to them.
Project name: led_port
Project location: CH7\led_port
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
int LEDs = 0x88;
int *ptr;
/* a variable representing LEDs */
/* a pointer */
UartInit();
ptr = &LEDs;
/* get the address of the LEDs */
printf("LEDS are set to %x\r\n", *ptr);
*ptr = 0x44;
printf("LEDS are set to %x", *ptr);
}
while(1) {
}
● 135
C Programming with Arduino
Program Output
LEDS are set to 88
LEDS are set to 44
Here we are just simulating a port connected to LEDs by using a variable, but normally in
an embedded system the LED port will be at a specified address. The hardware engineer
who designed the system would have allocated it to the address, and he would have to
specify that the port is at, for example, address 0x0500. In the above code we don’t have
any control of the exact location in memory that the build process puts the variable, so
the address of the variable is obtained when the program is running. Let’s see how it
works.
A pointer variable is defined near the top of the body of main(): int *ptr;
This tells the compiler that we want to store the address of an integer in this pointer
variable. The next line assigns the address of the variable LEDs to the pointer variable.
We can now use the pointer to access the contents of the variable LEDs. Putting an
asterisk (*) in front of the pointer variable’s name dereferences the pointer, in other
words it accesses the data that the pointer is pointing to.
This example may not look very impressive as you could just as easily use the variable
LEDs to access the contents of this variable, but in a practical embedded system, the first
few lines of the program:
int LEDs = 0x88;
int *ptr;
ptr = &LEDs;
would be replaced by assigning a specific address to the pointer e.g. 0xF430. You would
now use *ptr to access the LEDs that you would otherwise have no way of accessing.
A further example should help to clarify the use of pointers in C.
Project name: pointer
Project location: CH7\pointer
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
int data = 123; /* an integer variable */
int *ptr;
/* a pointer variable (points to an integer) */
UartInit();
ptr = &data;
/* get the address of data */
printf("data contains %d\r\n", data);
printf("data contains %d\r\n", *ptr);
printf("Address of data is %d\r\n", &data);
printf("Address of data is %d\r\n", ptr);
printf("Address of ptr is %d\r\n", &ptr);
}
while(1) {
}
● 136
Chapter 7 • Memory and Microcontrollers
Example Program Output for UNO
data contains 123
data contains 123
Address of data is 2296
Address of data is 2296
Address of ptr is 2298
In the pointer program the pointer ptr is made to point to the variable data. Contents
of the variable data are then printed out in the manner that you are used to. The second
printf() statement in the program also prints out the value of data, but this time by
using the pointer. The address of the variable data was saved to the pointer variable ptr
and we are dereferencing the pointer when we use *ptr in the printf() statement. *ptr
means "Get the data that the address stored in ptr is pointing to".
The third printf() statement prints out the address of the variable data. After this the
contents of the variable ptr is printed which is also the address of the variable data as
we would expect because we assigned it this value at the beginning of the program.
In other words ptr is holding the address of the variable data. Finally the address of
the pointer variable ptr is printed out. ptr is also a variable so it must be located in
memory. If it is located in memory, it must have an address. This pointer variable just
contains the address of an integer. See Figure 7-6 for a graphical explanation of the
pointer program.
Note in Figure 7-6 that the memory locations that are shown are 16 bits wide – in other
words 2 bytes wide, as memory is always referred to in bytes. The pointer variable
ptr and the integer variable data are shown consecutively in memory. This will not
necessarily always be the case as the build process will decide where to locate these
variables in memory. Because these variables are placed consecutively in memory in
this example and are each 16 bits wide, the addresses are separated by 2 bytes (or 2
addresses), this is why the second address is 2298 (2296 + 2) and not 2297 (2296 + 1).
Getting back to the program counter in a microprocessor, in an 8-bit processor the
address in this register will be incremented by 1 each time to fetch each 8-bit instruction
from memory. A 32 bit processor will fetch 32-bit wide instructions, but addressing is still
in bytes so the program counter will be incremented by 4 each time to get the next 32-bit
instruction (4 bytes).
Figure 7-6 The pointer Program’s Pointer and Integer Variables in Memory
Hardware on an embedded system consists of registers from a programmer’s point of
view. A register is just like a single memory location on a processor. These registers
are accessible in a C program by using pointers. Hardware is at a fixed address and we
therefore can’t use a variable name to access the hardware, but must use a pointer that
contains the address of the hardware and thereby points to it. This will become clearer in
the chapters that follow.
● 137
C Programming with Arduino
7.5 • More on C Data Types
So far we have seen four data types: int, long, float and char. Each of these data
types have a fixed width (in bytes) and can also be used in conjunction with other
keywords to change whether they can be positive and negative (signed) or positive only
numbers (unsigned). Their size determines how many bytes of memory they will occupy
and their sign determines which range of values they can hold.
The size of an integer is not fixed in the C standard, but depends on how the compiler
defines it. The integer size is usually the same size as the maximum data size of the
processor that it runs on, except for 8-bit devices that usually have a 16-bit integer. A
16-bit microcontroller will usually have a 16-bit integer size; a 32-bit microcontroller will
usually have a 32-bit integer size. An integer is by default a signed integer i.e. it can store
positive and negative numbers.
Memory on a 32-bit microcontroller is 32 bits wide which means that four bytes can be
accessed with one read or write operation. An 8-bit microcontroller, like 8-bit AVRs, can
only read or write 8 bits at a time (one byte).
A signed 16-bit integer can store numbers in the range of -32,768 to +32,767. (-(216 ÷
2) to (216 ÷ 2) – 1). An unsigned 16-bit integer can hold numbers from 0 to 65,535 (216
– 1). This is because a single bit in the integer is used as the sign bit and is not needed
when the number is unsigned.
Even a char data type holds an integer value as we have already seen. The integer value
of a char can be displayed as a character if desired and character values can be assigned
to it, but they are all ultimately integer numbers on the microcontroller. Table 7-3 shows
the integer data types in C. The sizes in the table are the actual sizes of these variables
in C programs compiled with the GNU C compiler for 8-bit AVR microcontrollers in Atmel
Studio.
Table 7-3 C Integer Data Types
Type
Size
Minimum Value
Maximum Value
signed char
1 byte
-128
127
unsigned char or char
1 byte
0
255
short
2 bytes
-32,768
32,767
unsigned short
2 bytes
0
65,535
int
2 bytes
-32,768
32,767
unsigned int
2 bytes
0
65,535
long
4 bytes
-2,147,483,648
2,147,483,647
unsigned long
4 bytes
0
4,294,967,295
C compilers can choose whether a char is signed or unsigned by default. Most compilers
treat a char as signed by default, but this is not the case with Atmel Studio which treats
a char as an unsigned number by default, meaning that it can’t store a signed number,
only positive numbers from 0 to 255. The default setting can be changed in Atmel Studio
from the top menu by clicking Project → <project name> Properties... and then
clicking the Toolchain tab on the left of the page that opens, and finally clicking General
under AVR/GNU C Compiler as shown in Figure 7-7 on page 139.
● 138
Chapter 7 • Memory and Microcontrollers
Figure 7-7 The Default Sign of a char can be Changed if Needed
If a signed char is needed in a program and the compiler default is not changed to
unsigned, the char data type must then be written as unsigned char.
The fact that an int data type and a short data type are the same length may seem
odd, but this is dependent on the compiler and the processor architecture on which the
program is run, some compilers may use a 16-bit short and 32-bit int. The keyword
signed can be used with each of the integer data types, but the compiler treats each data
type as signed unless unsigned is specified, except for char.
The varsize program uses the sizeof keyword to obtain the size of each data type in
bytes. You can use this keyword if you are not sure what size the data types are on the
compiler that you are using.
Project name: varsize
Project location: CH7\varsize
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
UartInit();
printf("On this microcontroller,\r\n");
printf("A char is %d byte, or %d bits\r\n",
sizeof(char), sizeof(char) * 8);
printf("A short is %d bytes or %d bits\r\n",
sizeof(short), sizeof(short) * 8);
printf("A int is %d bytes or %d bits\r\n",
sizeof(int), sizeof(int) * 8);
printf("A long is %d bytes or %d bits\r\n",
sizeof(long), sizeof(long) * 8);
}
while(1) {
}
● 139
C Programming with Arduino
Program Output
On this microcontroller,
A char is 1 byte, or 8 bits
A short is 2 bytes or 16 bits
A int is 2 bytes or 16 bits
A long is 4 bytes or 32 bits
This program prints out the size of each integer data type in bytes and in bits. Because
a byte consists of eight bits, the number of bits that a data type consists of can be
calculated by multiplying the number of bytes returned by sizeof by eight.
The vardef program shows how to use each of the integer types from Table 7-3 on page
138 to define a variable and display the number that the variable is initialised to.
Project name: vardef
Project location: CH7\vardef
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
signed char
unsigned char
short
unsigned short
int
unsigned int
long
unsigned long
ch = -100;
uch = 0xff;
shrt = 20;
ushrt = 0xffff;
in = -10000;
uin = 0xffff;
lng = 100000001;
ulng = 0xffffffff;
UartInit();
printf("signed char
printf("unsigned char
printf("short
printf("unsigned short
printf("int
printf("unsigned int
printf("long
printf("unsigned long
}
=
=
=
=
=
=
=
=
%hd\r\n", ch);
%hu\r\n", uch);
%hd\r\n", shrt);
%hu\r\n", ushrt);
%d\r\n", in);
%u\r\n", uin);
%ld\r\n", lng);
%lu\r\n", ulng);
while(1) {
}
Program Output
signed char
unsigned char
short
unsigned short
int
unsigned int
long
unsigned long
● 140
=
=
=
=
=
=
=
=
-100
255
20
65535
-10000
65535
100000001
4294967295
Chapter 7 • Memory and Microcontrollers
In the vardef program the unsigned variables are initialised with the maximum number of
binary 1s that each type can hold by using the equivalent hexadecimal number. When the
value is printed out, it can be seen that it corresponds to the maximum value shown in
Table 7-3 on page 138.
The format specifier %u is used for all unsigned numbers, %ld (long decimal) is used to
print out the long data type and the %lu (long unsigned) format specifier is used to print
out the unsigned long data type. Format specifiers for the char and short data types
are preceded by the letter h. The l (lower case L) and h in these format specifiers are
known as modifiers as they modify the decimal, unsigned and char format specifiers for
printing the correct data types with the printf() function.
Format specifiers for integers are shown in Table 7-4. Each format specifier in the table,
except for %c, can be preceded with the letter ‘l’ after the % when the data type is long.
These same format specifiers can be preceded with the letter ‘h’ when printing short or
char data types.
Table 7-4 Integer Format Specifiers
Format Specificier
Data Type
Print Format
%d or %i
int
Decimal number
%u
unsigned int
Unsigned decimal number
%x
unsigned int
Lowercase hexadecimal
%X
unsigned int
Uppercase hexadecimal
%c
char or int
Character
Table 7-5 shows what the data types from the above program look like in memory for the
Arduino Uno / ATmega328P microcontroller. Each of the cells in the diagram represents
one byte, so an int will take up two of these cells to make up its 16 bits. Bytes that make
up a number that is larger than one byte will always be successive bytes in memory, but
the individual variables will not necessarily be placed consecutively in memory, e.g. if the
first byte of a 32-bit number occurs at address 2292, the other 3 bytes will occur at the
addresses 2293, 2294 and 2295 to make up all 32 bits or 4 bytes of the number.
Table 7-5 Integer Data Types in Memory
Address
Variable Name
Data in Memory
Data Type
ch
-100
signed char
0x08eb (2283)
uch
0xff
unsigned char
0x08ec (2284)
shrt
20
short
ushrt
0xffff
unsigned short
in
-10000
int
uin
0xffff
unsigned int
0x08ed (2285)
0x08ee (2286)
0x08ef (2287)
0x08f0 (2288)
0x08f1 (2289)
0x08f2 (2290)
0x08f3 (2291)
● 141
C Programming with Arduino
Address
Variable Name
Data in Memory
Data Type
0x08f4 (2292)
lng
100000001
long
ulng
0xffffffff
unsigned long
0x08f5 (2293)
0x08F6 (2294)
0x08f7 (2295)
0x08f8 (2296)
0x08f9 (2297)
0x08fa (2298)
0x08fb (2299)
7.6 • Microcontroller Memory Sizes
Information on a microcontroller such as the size of memory and number of different
memory types can be found in the microcontroller’s datasheet. Datasheets for
microcontrollers tend to be rather large, but contain information about the internal
microprocessor and all of the microcontroller’s peripheral devices.
Datasheets for Atmel microcontrollers can be found by visiting the main Atmel website
at www.atmel.com and then finding the microcontroller family that the device belongs to
and then the individual page for the specific microcontroller part number which contains
a link to the datasheet. For the microcontrollers on the Uno and MEGA Arduino boards
the microcontroller family is chosen from the Atmel home page by selecting Products
→ AVR 8- and 32-bit MCUs → megaAVR MCUs which then lists all the megaAVR
microcontrollers on the web page. The individual page for each microcontroller can then
be found by scrolling down the page to find the correct part number or by using the web
browser’s search (Ctrl + F). A new Arduino Uno board contains an ATmega328P and the
Arduino MEGA 2560 contains an ATmega2560 part.
Datasheets are usually shared between microcontrollers with similar features that
often only differ in their memory sizes and physical packages, so several different part
numbers will be displayed at the top of the datasheet when it is opened. Package sizes
of microcontrollers usually influence the number of pins that the microcontroller has,
which in turn influences the number of peripheral devices that a microcontroller has.
A microcontroller that is housed in a package with fewer pins will usually have some
peripheral devices missing when compared to the same microcontroller in a package
that has more pins. These microcontrollers will typically have different part numbers, but
share the same datasheet and be compared in a table in the datasheet.
Table 7-6 compares the memory sizes of the microcontroller found on the Arduino Uno
and MEGA 2560, although theses devices do not share a datasheet, but each have an
individual datasheet.
Table 7-6 Comparison of Memory Types on Uno and MEGA 2560
Arduino
µC Part Number
Flash (ROM)
SRAM (RAM)
EEPROM
Uno
ATmega328P
32k Bytes
2k Bytes
1k Bytes
MEGA 2560
ATmega2560
256k Bytes
8k Bytes
4k Bytes
From the table it can be seen that the MEGA has a lot more ROM memory in the form of
Flash than the Uno. RAM in the form of SRAM (Static Random Access Memory) which is
used for storing variables is also more plentiful on the MEGA. EEPROM is a type of nonvolatile memory that can be updated by a C program at runtime and is used to store data
● 142
Chapter 7 • Memory and Microcontrollers
that must be preserved between power cycles – in other words between switching power
to the board off and on.
7.7 • Summary
•
Two basic memory types are used on embedded systems: ROM and RAM.
•
ROM is non-volatile memory and keeps its contents even after the power to it
has been switched off.
•
RAM is volatile memory and looses its contents when the power to it is switched
off.
•
Memory is arranged as consecutive bytes in a computer or embedded system.
•
Each byte in memory has its own address and can be accessed by putting this
address on its address bus.
•
Data is written to or read from memory using the data bus.
•
The control bus contains signals to move data into or out of the memory chip.
•
Pointers are used in C to access memory using the address of the memory.
•
Each variable in C stores data and has an address in memory.
•
C integer data types can be used to create and access variables of different bit
widths.
•
The width of each integer variable in bits determines the size range of the
number that it can hold.
● 143
C Programming with Arduino
● 144
Chapter 8 • Accessing AVR Ports in C
Chapter 8 • Accessing AVR Ports in C
What you will do in this chapter:
•
Write to AVR hardware registers using pointers
•
Set an AVR microcontroller port as an output port
•
Switch LEDs on and off using an AVR register
•
Learn about the increment and decrement operator
•
Start a new Atmel Studio project from scratch without using a template
8.1 • AVR Pins and Ports
AVR microcontrollers have a number of pins that are known as general purpose input/
output (I/O) pins. These pins can be individually configured as either output pins to
drive a load – e.g. to drive or switch an LED on and off; or as input pins used to read a
logic level placed on the pin – e.g. to test whether an attached switch is open or closed.
Additionally, these pins have an alternative function in that they can be configured to
connect to an internal hardware device, such as a UART used for serial communications or
ADC to read analogue values. In this chapter, we will look at configuring pins as outputs
to drive LEDs.
AVR 8-bit microcontrollers have 8-bit ports which are groups of I/O pins that are named
with letters – e.g. port A, port B, port C, etc. These ports are each controlled by their own
set of registers that allow the pins to be configured as inputs or outputs. Registers are
just like memory locations and can therefore be written to using pointers in C. The actual
names and functions of the registers can be found in the microcontroller’s datasheet.
Figure 8-1 ATmega328P Pin Numbering and Ports
Figure 8-1 shows a top view of the physical package of an ATmega328P microcontroller
found on the Arduino Uno showing the pin numbering (pin 1 to pin 28) and individual
numbering of pins that make up the ports, namely PB0 to PB7, PC0 to PC6 and PD0 to
PD7. At the right of the figure, the ports are shown in a block diagram with port B and
port D being 8 bits wide and port C being 7 bits wide.
Figure 8-2 on page 146 shows a block diagram of the pins and ports of an ATmega2560
microcontroller found on the Arduino MEGA 2560.
● 145
C Programming with Arduino
Figure 8-2 ATmega2560 Ports
The above figures show unused ports of the microcontrollers, however, when the
microcontrollers are placed on a board, such as the Arduino, some of the port pins are
dedicated to certain functions such as the serial port. The figures that follow show which
port pins are used for dedicated functions on the Arduino boards and which pins are free
for general purpose use.
Figure 8-3 ATmega328P on the Arduino Uno with Used Pins (L) and Free Pins (R)
Figure 8-3 shows port pins of the ATmega328P that are used by the Arduino Uno board
on the left of the figure. Pins that are available for general purpose use are shown on the
right. PB3, PB4 and PB5 are used by the ICSP header along with the reset pin PC6. PC6,
or reset, is connected to both the ICSP header and the reset button on the board. The
ICSP header is the header that a programming device such as the AVRISP or AVR Dragon
is connected to for programming. PB5 of the ICSP header is also connected to the onboard LED labelled L on the board which maps to Arduino pin 13.
● 146
Chapter 8 • Accessing AVR Ports in C
PB6 and PB7 are connected to an external 16MHz ceramic resonator used to generate the
main clock source for the AVR. PD0 and PD1 are dedicated to the on-chip UART which is
connected to the USB port of the Arduino through a second AVR chip on the board. These
pins are used for sending and receiving data between the Arduino and the PC and have
been used to connect to the terminal program in the example programs in this book.
Figure 8-4 ATmega2560 on the Arduino MEGA 2560 with Unused 8-bit Ports on
the Right
Figure 8-4 shows the ATmega2560 found on the Arduino MEGA 2560 with full 8-bit ports
on the right and used ports on the left. In the case of the MEGA board, there are more
I/O pins on the ATmega2560 chip than there are connection points on the board, which
means that some of the pins of the chip are not connected to anything. The ATmega2560
chip has ten 8-bit ports and one 6-bit port which are reduced to five 8-bit ports and six
ports with less than 8 pins when the chip is placed on the Arduino MEGA.
Three pins of port B are used by the ICSP header along with a dedicated reset pin which
can’t be configured as an I/O pin and is not shown in the figure. A fourth pin from port
B (pin PB7) is used for the on-board L LED of the Arduino and maps to pin 13 of the
Arduino’s connectors.
Port D has three pins that are not connected, reducing it to a 5-bit port. Port E has three
pins that are not connected and two pins that are dedicated to the UART for USB / serial
port communications with the PC, leaving three spare pins. Port G was originally only
6 bits wide, but has two unconnected pins reducing it to a 4-bit port. Port H has two
unconnected pins and port J has six unconnected pins.
● 147
C Programming with Arduino
When connecting hardware such as LEDs, switches or other devices to the connectors
on the Arduino, it is necessary to know which port pins map to which Arduino connector
pins. The reason for this is that a C program references the ports by port name and not
Arduino pin number. AVR port to Arduino pin mappings are shown as they are used in the
C programs that follow.
Full pin mappings of AVR port pins to Arduino header / connector pin numbers can be
found in Appendix B on page 331 for both the Arduino Uno and Mega 2560. When
the Arduino IDE is used to program Arduino boards, software is used to hide the AVR
microcontroller port pin numbering and to give Arduino pins the same numbers across
different Arduino boards even if they occur on different AVR port pins. An example of
using software to give Arduino pins their Arduino pin numbers is pin 13 on the Uno and
MEGA that is connected to the on-board L LED which occurs on AVR port pin PB5 on the
Uno AVR, but AVR port pin PB7 on the MEGA AVR.
8.2 • Switching the On-board LED On and Off
To switch an LED on and off that is attached to a pin of an AVR port, we need to write
values to two AVR registers. Firstly we must write to the DDR (Data Direction Register) to
set the pin as an output pin – by default when the microcontroller is powered up, all pins
are set as inputs. Secondly, to switch the LED on and off we need to write values to the
PORT data register.
Each port has its own set of registers for controlling I/O port functions. To access the
DDR of port A, the DDRA (Data Direction Register A) is used, to access the DDR of port
B, DDRB is used, etc. The same occurs for the PORT register, e.g. PORTA, PORTB. These
register names have already been defined and can be used just like variables. When the
file avr/io.h is included in a C file, the AVR registers are available in that file.
Figure 8-5 on page 149 shows the registers needed to control the on-board LED of the
Arduino Uno. Arduino Uno pin number 13, which the L LED is attached to, maps to AVR
pin PB5. On the MEGA the L LED is also attached to pin 13, but Arduino pin 13 maps to
PB7 of the AVR and is shown in Figure 8-6 on page 150. For the Uno, pin PB5 maps to
bit number 5 in each of the registers. Writing a logic 1 to bit 5 of DDRB sets up PB5 as
an output pin. On the MEGA, the LED is attached to pin PB7, making it necessary to set
bit number 7 in DDRB to configure the pin as an output pin. The hexadecimal number to
write to the register is therefore 0x20 for the Uno and 0x80 for the MEGA.
To switch the LED on, the same corresponding bit must be set to a logic 1, but this
time in the PORTB register. Writing 0x20 to PORTB on the Uno switches the LED on. As
Figure 8-5 on page 149 shows, the LED can be thought of as being connected to a
single logic cell in the register. Writing a logic 1 to the register is the same as putting 5V
onto the LED, switching it on. Writing 0x80 to the PORTB register on the MEGA will switch
its LED on. The binary numbers in the registers which are 00100000B for the Uno and
10000000B for the MEGA can be converted to their hexadecimal equivalents using the
Windows calculator. To switch the LED off, a 0 logic level is written to the corresponding
bit in the register by writing 0x00 to the register.
AVR Ports in the Datasheet
A full description of the I/O ports and their functionality can be found in the I/O-Ports
chapter of the AVR datasheet. I/O port registers and the name of each bit in these
registers can be found at the end of the I/O-Ports chapter of the datasheet in the
Register Description section.
● 148
Chapter 8 • Accessing AVR Ports in C
The example C programs that follow will make the above explanation clearer. Because the
Uno and MEGA have their on-board LEDs connected to different port pins, two different
programs have been included – the first is for the Uno and the second for the MEGA.
Figure 8-7 on page 151 shows the position of the L LED on the Arduino Uno and MEGA
boards.
Figure 8-5 ATmega328P Registers for Controlling a LED on an Arduino Uno
Project name: port_write_uno
Project location: CH8\port_write_uno
main.c
#include <avr/io.h>
void Delay(void);
int main(void)
{
DDRB = 0x20;
// set bit 5 of DDR register which makes PB5 an output
}
while(1)
{
PORTB = 0x20;
// switch LED on
Delay();
PORTB = 0x00;
// switch LED off
Delay();
}
void Delay(void)
{
volatile unsigned long count = 100000;
}
while (count--);
Program Output for Both Programs
The on-board L LED flashes on and off or 'blinks'.
● 149
C Programming with Arduino
Figure 8-6 ATmega2560 Registers for Controlling a LED on an Arduino MEGA 2560
Project name: port_write_mega
Project location: CH8\port_write_mega
main.c
#include <avr/io.h>
void Delay(void);
int main(void)
{
DDRB = 0x80;
// set bit 7 of DDR register which makes PB7 an output
}
while(1)
{
PORTB = 0x80;
// switch LED on
Delay();
PORTB = 0x00;
// switch LED off
Delay();
}
void Delay(void)
{
volatile unsigned long count = 100000;
}
while (count--);
The two programs for controlling the LEDs should be self explanatory, except for the
decrement operator used in the delay function that will be explained shortly. The only
difference between the two programs is the number written to the PORTB register and
DDRB. As can be seen, the two registers discussed earlier are accessed just as if they
were variable names, although what they boil down to is accessing registers using
pointers. The io.h file included at the beginning of the program makes these register
names available and convenient to access.
● 150
Chapter 8 • Accessing AVR Ports in C
Figure 8-7 Position of the L LED on the Arduino Uno (left) and MEGA 2560 (right)
Writing a logic 1 to the bit position that corresponds to the desired port pin number in
DDRB sets that pin as an output pin. In the while(1) loop, the PORTB register is written
to in order to switch the LED on by setting the corresponding bit in the register to logic 1
and then written to again to switch the LED off by clearing the corresponding bit to logic
0. Calls to the Delay() function between switching the LED on and off ensure that the
LED remains in the on and then off state long enough to be visible to the eye.
In the Delay() function, the decrement operator (--) is used to decrease the value that
the variable count is holding by one and has been included in the loop expression of the
while loop. Written on its own, the decrement operator looks as follows when operating
on a variable:
count--;
The above statement is a shorthand way of writing:
count = count - 1;
When inserted into the loop as in the program:
while (count--);
This statement will evaluate to true as long as count contains a value greater than zero.
Each time through the loop, count is evaluated and then decremented. The value that
count holds will eventually become zero, and when the loop expression is evaluated again
will be false, breaking program flow out of the loop. The above statement could also be
written as follows:
while (count) {
count--;
}
Or when written without the decrement operator:
while (count) {
count = count - 1;
}
The time taken to decrement the number from a big value to zero is what generates the
time delay that leaves the LED on for a period of time and then off for a period of time,
in the same way as the delay function from the flashing LED simulation program from
Chapter 5 (on page 97) did.
● 151
C Programming with Arduino
The volatile keyword has been used again in front of the variable definition in the
Delay() function as follows:
volatile unsigned long count = 100000;
As a reminder, volatile is used so that the compiler optimisation process does not
optimise this variable away. Compilers have optimisation settings that can be used to
reduce the code size of a compiled program and/or increase program execution speed.
The compiler may see that the loop that count is used in actually does nothing and may
try to optimise the loop by removing it. Not using the volatile keyword when required
can be the source of a seemingly mysterious error in embedded programming where
a program that has no errors does not work as expected. If volatile is left out of the
previous programs, the LED will appear to remain in the on state, although it is switching
on and off too rapidly for the eye to perceive. An alternative to using the volatile
keyword is to turn compiler optimisation off as is shown in the chapter on debugging,
although it is better to use the volatile keyword no matter what the compiler settings
are because anyone can change the optimisation settings and recompile the program.
It is important to note that the above programs can be written more effectively using
bitwise operators to switch the LED on and off. Bitwise operators are still to be covered.
One of the problems with writing to all the bits in a register when only one bit needs to be
written to is that the state of all the other bits is also changed. It would be better to leave
the other bits unchanged. When writing 0x20 to a register, bit 5 is set to logic 1, but
all other bits are cleared to logic 0 – we only wanted to change the value of one bit but
ended up changing the value of all bits. The same happens when bit 5 is cleared, all bits
are cleared as well. This is not a problem if we are writing to a full 8- bit port and want
to write to all 8 bits, and did not matter in the previous programs because the other pins
on the port were unused. In the case of microcontrollers it is often desirable to use only
some of the pins of a port as outputs and use other pins from the same port as inputs or
alternative functions – bitwise operators can then be used to change the value of a single
bit of a port register without changing the values of other bits in the port register.
8.3 • Controlling LEDs interfaced to a Port
In the next example program, LEDs are connected to port C of the Arduino. On the MEGA,
port C is a full 8-bit port, but on the Uno only 6 pins are available from port C. The upper
two bits of port C on the Arduino Uno are ignored and only six LEDs are interfaced to the
port. On the MEGA, all 8 pins of port C are interfaced to LEDs.
8.3.1 • Connecting the Hardware
LED connections to the Arduino Uno and Arduino MEGA are shown in the figures below.
A current limiting 470Ω resistor is placed in series with each LED connected to the port.
Figure 8-8 on page 153 shows six LEDs connected to port C of an Arduino Uno using an
electronic breadboard, jumper wires and resistors and Figure 8-9 on page 153 shows
eight LEDs connected to port C on an Arduino MEGA in the same way.
● 152
Chapter 8 • Accessing AVR Ports in C
Figure 8-8 Connecting 6 LEDS to Port C of the Arduino Uno AVR
Figure 8-9 Connecting 8 LEDs to Port C of the Arduino MEGA AVR
Circuit diagrams for the Arduino Uno and Arduino MEGA are shown in Figure 8-10 on
page 154 and Figure 8-11 on page 154 respectively. The circuit diagrams also show
how the AVR port pins map to the Arduino connector pins. All of the available port C pins
on the Arduino Uno map to analogue pins A0 to A5. These pins are configurable as I/O
pins and have an alternative function of being analogue pins as well, so it is perfectly
acceptable to used them as output pins.
Connect the LEDs as shown to your Arduino in order to run the C program that follows. It
does not matter whether the resistor or LED is connected to the Arduino first, as long as
they are in series. Don’t forget to connect the Arduino board common GND pin to each of
the resistors or LEDS, depending on in which order you wired these components to the
Arduino.
Using Port C
Port C has been chosen to use with most of the example programs that connect to LEDs
and switches because this port is available on both the Arduino Uno and Arduino MEGA,
although not all of the port pins of port C are available on the Uno.
Using the same port on the Uno and MEGA means that the same C program can be used
● 153
C Programming with Arduino
on both boards, otherwise two versions of every program would need to be written – one
for the port used on the Uno and one for the port used on the MEGA.
Figure 8-10 Arduino Uno Circuit and Pin Mapping
Figure 8-11 Arduino MEGA 2560 Circuit and Pin Mapping
8.3.2 • Starting a New Atmel Studio Project from Scratch
Up until now, all the Atmel Studio projects have been created using the templates that
were set up to enable the printf() and scanf() functions to work on the Arduino. These
functions are used for teaching purposes and won’t be used on most practical embedded
system application. Most new Atmel Studio projects will be created from scratch as this
section shows. There is not much difference between creating a project from a template
and creating a project from scratch as shown in the steps below.
● 154
Chapter 8 • Accessing AVR Ports in C
1.
Start Atmel Studio and create a new project using the toolbar icon or File →
New → Project... from the top menu bar.
2.
In the New Project dialog box, click GCC Executable Project as shown in
Figure 8-12 and give the new project a name as usual. Click OK when done.
3.
Choose the correct microcontroller for the board being used. Typing the part
number in the search box of the Device Selection dialog box is easiest,
as shown in Figure 8-13 on page 156. Type atmega328p for the Uno or
atmega2560 for the MEGA.
4.
After searching for the correct part in the Device Selection dialog box, click the
part in the left pane and then click the OK button (figure 8.13).
5.
The project will be created with some skeleton code in the main C file which is
automatically opened for editing.
6.
Use the No Tool button on the second top toolbar at the right to select the tool
(e.g. AVR Dragon or AVRISP) to program the board with (Figure 8-14 on page
156 and Figure 8-15 on page 157). The tool must be plugged into the USB
port of the PC before it can be selected.
7.
Click the Save All icon to save the new project, or use File → Save All from the
top menu. The project is now ready to add the code shown in the listing below.
Figure 8-12 Creating a New Atmel Studio Project from Scratch
● 155
C Programming with Arduino
Figure 8-13 Selecting the AVR Device for the Arduino Board in Use
Figure 8-14 Selecting the Programming Tool from the Toolbar in Atmel Studio
Clicking "No Tool" on the toolbar next to the hammer icon will open the page shown in
Figure 8-15 on page 157 which allows a programming tool to be selected from the
drop-down list. Select either the AVR ISP tool or the AVR Dragon and ISP for the interface
setting. After the selection has been made, the selected tool name will appear in place of
the "No Tool" text on the toolbar shown in Figure 8-14.
● 156
Chapter 8 • Accessing AVR Ports in C
Figure 8-15 Selecting the Programming Tool
8.3.3 • LED Port Control Program
After creating a new project as described above, add the following code to the main C file,
compile and load to the Arduino. This program displays a binary count value on the LEDs
that were connected to port C of the AVR in the previous section.
Project name: LED_port
Project location: CH8\LED_port
main.c
#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
int main(void)
{
// value to write to port
unsigned char port_val = 0;
// all port C pins set as outputs
DDRC = 0xFF;
}
while(1)
{
PORTC = port_val;
port_val++;
_delay_ms(1000);
}
// write value to port
// increment value
// 1 second delay
Program Output
Binary count is displayed on the LEDs.
In the LED_port program, avr/io.h is included in the C source file so that we have access
to the AVR I/O registers. A second header file, util/delay.h, is also included in order to
provide a delay function called _delay_ms() which creates a delay in milliseconds, with
● 157
C Programming with Arduino
the number of milliseconds to delay passed to this function. This delay library function
saves us from having to write our own delay function as we did in the previous program.
In order for the delay function to accurately create the delay time, it needs to know at
what speed the main system clock is running. In the case of both the Uno and MEGA, the
main clock runs at 16MHz or 16,000,000Hz. Defining a value for F_CPU at the top of the C
source file provides the clock value to the delay function. Because the clock runs at
16MHz, F_CPU is defined with a value of 16 million or 16,000,000 as follows.
#define F_CPU 16000000UL
The above line of code defines F_CPU with a value of 16000000 unsigned long (UL).
The object of this program is to display a binary count value on the LEDs that are
interfaced to an AVR port. The width of the port is 8 bits, so an unsigned char variable
called port_val is defined to hold the count. An unsigned char was chosen because the
number will never be negative and a char is 8 bits wide. After defining the variable, all
bits in DDRC are set to logic level 1 by writing 0xFF to this register which is 11111111B.
This value written to DDRC configures all of the pins of port C as output pins.
Inside the continuous while(1) loop, the value in the variable port_val, which is initially
zero, is placed on port C using the following line of code.
PORTC = port_val;
The first time through the loop all of the LEDs are switched off because the port_val
variable contains zero.
The increment operator is used to increase the value of port_val by one. The increment
operator is similar to the decrement operator shown in the previous program, but instead
of decreasing the value of a variable by one, it increases the value by one.
port_val++;
The increment operator ++ used on the variable shown above is the equivalent of the
following line of code.
port_val = port_val + 1;
After the variable has been incremented by one, the delay library function _delay_ms()
is called and passed a value of 1000. This 1000 means that the delay function will delay
for 1000ms (one thousand milliseconds) which creates a 1 second delay (one second
contains one thousand milliseconds).
When program execution starts at the top of the loop again, port_val contains the value
1 which is written to the LEDs. This switches the least significant bit of port C on and the
LED attached to port pin PC0. Each time through the loop, the variable is incremented
by one and the new value is displayed on the LEDs. In this way the binary value in the
variable is displayed on the LEDs where an on LED represents binary 1 and an off LED
represents binary 0.
When the value in variable port_val is incremented enough times and becomes 255
or 0xFF, all of the bits in this variable are logic 1s. The next time that the variable is
incremented, it overflows and all the bits become zero – 0 or 0x00. In this way, the
binary count automatically restarts from zero each time the maximum count value that
an 8-bit number can hold is reached.
● 158
Chapter 8 • Accessing AVR Ports in C
On the Arduino Uno with only 6 LEDs on the port, the binary count appears to work
properly, but only counts up to 0x3F, 111111b or 63 (26 – 1). What is actually happening
is that the top two, or two most significant bits of the number are not displayed on LEDs.
The full count from 0 to 255 is still happening in the port_val variable, but because the
binary count of the lower 6 bits always follows the same pattern, the missing two bits are
not noticed.
When looking at the binary value on the LEDs, make sure that the board and LEDs are
not upside down in which case the LSB LED would be on the left and the binary count
sequence would appear to be incorrect.
8.4 • Increment and Decrement Operators
Both the increment and decrement operators have been shown and explained in the
previous two example programs. What has not been shown is that the increment operator
++ and the decrement operator -- can be placed either before the variable being
operated on or after the variable being operated on as the following code shows for the
increment operator.
++port_val;
port_val++;
When used in the above manner it makes no difference whether the increment operator
is placed before or after the variable. When it does matter is when the increment or
decrement operator is evaluated, for example in an if statement or while loop.
8.4.1 • Pre-increment and Pre-decrement
When an increment operator is placed before a variable (known as pre-increment) and
appears in a statement that is evaluated, the value that the variable holds will first be
incremented by one and then evaluated. The same applies to the decrement operator
placed before a variable (pre-decrement) the variable will first be decremented by one
and then evaluated.
8.4.2 • Post-increment and Post-decrement
When an increment operator is placed after a variable (know as post-increment) and
appears in a statement that is evaluated, the value that the variable holds will first be
evaluated and then incremented by one. When a decrement operator is placed after a
variable (post-decrement) the variable is first evaluated and then decremented by one.
The next program called increment shows how the post-increment and pre-increment
operators work. Create the project from the template file and use the serial port terminal
program to view the output from the program.
● 159
C Programming with Arduino
Project name: increment
Project location: CH8\increment
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
int num = 0;
UartInit();
printf("num is %d\r\n", num);
// num is 0
printf("Post-increment: %d\r\n", num++);
// num is 0
printf("After increment: %d\r\n", num);
// num is 1
printf("Pre-increment: %d\r\n", ++num);
// num is 2
printf("After increment: %d\r\n", num);
// num is 2
}
while(1) {
}
Program Output
num is 0
Post-increment: 0
After increment: 1
Pre-increment: 2
After increment: 2
In this program, the value of variable num is first printed out and has a value of 0 that it
was initialised with. In the second printf() statement, num is post-incremented which
means that num is first used in the statement and then incremented so that it will only
have the new incremented value in the next statement. For this reason this statement,
shown below, prints out 0 when the variable is incremented in the statement.
printf("Post-increment: %d\r\n", num++);
The new incremented value of num only takes effect in the next statement, shown below,
which simply prints out the value of num without incrementing it. The value printed is 1.
printf("After increment: %d\r\n", num);
When the pre-increment statement is used, as shown below, num is incremented before it
is used in the statement in which it occurs. The statement therefore increments the value
of num from 1 to 2 and prints out 2.
printf("Pre-increment: %d\r\n", ++num);
The final printf() statement prints the value of num again which is still 2 because the
increment operation took place in the previous statement.
● 160
Chapter 8 • Accessing AVR Ports in C
8.5 • Summary
•
To switch an LED on and off that is connected to a microcontroller’s port pin
requires knowledge of the microcontroller port’s hardware.
•
Hardware information, such as port configuration, port registers and bit
numbering, is found in a microcontroller’s datasheet.
•
Hardware differs between different microcontroller architectures and
manufacturers. Setting up microcontroller pins as outputs on an AVR
microcontroller differs from setting up microcontroller pins as outputs on an
ARM microcontroller.
•
When connecting LEDs to microcontroller pins, always use a current limiting
series resistor.
•
LEDs connected to a port of an AVR microcontroller can be switched on and
off by first using a register to configure the pins as outputs and then using a
different register to write to the LEDs to switch them on or off.
•
The increment and decrement operators are a shorthand way of increasing or
decreasing the value that a variable contains by 1.
•
Placing the increment or decrement operator before a variable name increases
or decreases the value of the variable by one before it is evaluated – this is
know as pre-incrementing or pre-decrementing the variable.
•
Placing the increment or decrement operator after a variable name increases
or decreases the value of the variable by one after it is evaluated – known as
post-incrementing or post-decrementing the variable.
● 161
C Programming with Arduino
● 162
Chapter 9 • I/O and Memory Maps
Chapter 9 • I/O and Memory Maps
What you will do in this chapter:
•
Read the state of a switch connected to a microcontroller input pin
•
Work with input and output pins
•
See how hardware and memory are mapped in AVR microcontrollers
9.1 • Input Pins
In the previous chapter we worked with output only by sending a value out of the
microcontroller to switch LEDs on and off that were connected to pins on the AVR’s port.
The port pins were set up as output pins and used to drive LEDs. Port pins can also be set
up as inputs and the logic state of these pins can be read by the microcontroller. A switch
connected to a microcontroller pin set up as an input can be read to see if the switch is
open or closed.
9.1.1 • Connecting a Switch to a Port Pin
In order to read the state of a switch in a C program, the switch must first be connected
to the desired port pin of the AVR microcontroller on the Arduino. The switch used for this
example can be almost any kind of switch that only has to close a circuit and then open a
circuit. A momentary push-button switch, toggle switch, key switch or even a wire can be
used to close and open a circuit.
Figure 9-1 Connecting a Switch to Pin PC0 on the Uno (left) and MEGA (right)
Figure 9-1 shows a switch interfaced to port pin PC0 on an Arduino Uno and MEGA, with
the only difference between the two circuits being the Arduino pin number that the switch
is connected to. The common GND at one end of the resistor is connected to the GND
connection on the Arduino.
● 163
C Programming with Arduino
When the switch is open, the 10k pull-down resistor connects the PC0 pin to GND or 0V of
the power supply putting a logic level 0 onto the pin. When the switch is closed, the pin is
shorted to 5V which places a logic level 1 onto the pin. The 10k resistor prevents the 5V
from being shorted to GND.
9.1.2 • Reading the State of a Switch in C
Create a new Atmel Studio project called switch_val using the template file and connect
a switch to the Arduino using a breadboard as shown in the circuit from Figure 9-1 on
page 163. Add the following code to the project.
Project name: switch_val
Project location: CH9\switch_val
main.c
#define
#include
#include
#include
#include
F_CPU 16000000UL
<avr/io.h>
<util/delay.h>
<stdio.h>
"stdio_setup.h"
int main(void)
{
DDRC = 0x00;
UartInit();
}
// set port C pins as outputs
while(1) {
printf("PORT C: 0x%02x\r\n", PINC);
// print the port state
_delay_ms(200);
}
Example Program Output
PORT
PORT
PORT
PORT
PORT
PORT
...
C:
C:
C:
C:
C:
C:
0x00
0x01
0x01
0x01
0x00
0x00
When the switch is open, the value of port C displayed in the terminal window is 0x00.
When the switch is closed, the value displayed is 0x01. At the beginning of the program,
DDRC is assigned a value of 0x00 to configure all of the port pins of port C as inputs. This
is actually not necessary because the port pins are configured as inputs by default, but it
does show the intention of the programmer that the entire port will be used as an input
port, although we are only using one pin of the input port in the example program.
In the while(1) loop, the value of the PINC register is printed to the terminal program
running on the PC. The value in this register is used directly in the printf() statement
without first reading it into an intermediate variable. The printf() statement is followed
by a 200ms delay that prevents the terminal program from being swamped with too
many consecutive messages.
The PINC register is another register that is part of a set of registers used for configuring,
reading and writing to AVR I/O ports. PINC is the register used for reading the value of
● 164
Chapter 9 • I/O and Memory Maps
port pins on port C. The logic state of pins on port A can be read using the PINA register,
pins on port B are read using the PINB register, etc. PINx (where x is the port letter A,
B, C, etc.) registers operate in the same way as PORTx registers in that one bit in the
register corresponds to one pin on the port. Register PORTC was used to write to LEDs
connected to port C when the port pins were set up as outputs. PINC is now used to read
the state of switches when the port pins are set up as inputs. Figure 9-2 shows how the
state of the switch changes the value in the PINC register.
Figure 9-2 Reading the State of a Switch using the PINC Register
Using the same C program and hardware, disconnect the switch from pin PC0 and
connect it in turn to PC1, then PC2, etc. When the switch is pressed, it will show the
hexadecimal number 0x01 when connected to PC0, 0x02 when connected to PC1, 0x04
when connected to PC2 and 0x08 when connected to PC3. What is happening is that
the weight of each bit position in the PINC register changes the number displayed. The
value in the PINC register is just a binary number that is being changed by the switch
connected to it. The number changes as a single bit is set to logic high or 1, while all
other bits remain logic low or 0 as follows.
Bit 0, PC0 20 = 1 or 0x01 (bit 0 high all other bits low)
Bit 1, PC1 21 = 2 or 0x02 (bit 1 high all other bits low)
Bit 2, PC2 22 = 4 or 0x04 (bit 2 high all other bits low)
Bit 3, PC3 23 = 8 or 0x08 (bit 3 high all other bits low)
This 1248 weighting continues in the upper nibble of the byte as the switch is connected
to PC4 to PC7. Moving the switch from PC4 through to PC7 will change the number to
0x10, 0x20, 0x40 and 0x80.
● 165
C Programming with Arduino
9.2 • Input and Output Pins on the Same Port
So far we have set up an entire port as either an output port or an input port. We will now
set up a single pin on a port as an output and a single pin as an input to control a LED
and read a switch in the same program.
9.2.1 • Connecting a Switch and LED
The circuit in Figure 9-3 below is the same as the circuit used in the previous program,
but adds a single LED and series resistor to one of the port pins. Build the circuit on
breadboard which will be controlled by the C program that follows.
Figure 9-3 Connecting a Switch and LED to Arduino Uno (left) and MEGA (right)
9.2.2 • Programming a Switch and LED
Start a new Atmel Studio project from scratch as explained in Chapter 8 on page 154
and add the following code to it.
Project name: switch_LED
Project location: CH9\switch_LED
main.c
#include <avr/io.h>
int main(void)
{
DDRC = 0x20;
while(1) {
if (PINC & 0x01) {
PORTC = 0x20;
}
else {
PORTC = 0x00;
● 166
// PC5 output, other pins inputs
// is the switch on PC0 closed?
// switch the LED on
// switch the LED off
Chapter 9 • I/O and Memory Maps
}
}
}
Program Output
Closing the switch switches the LED on. Opening the switch switches the LED off.
In this program, DDRC is used to set pin PC5 up as an output by setting bit number 5 in
this register, all other bits in the register are cleared to 0 making the corresponding pins
inputs.
In the while(1) loop, the PINC register is checked in the if statement to see if bit 0
contains a logic 1 which would mean that the switch is closed. If the switch is closed, the
LED connected to PC5 is switched on by setting bit 5 in the PORTC register. If the switch
is open then the code in the else block is run which switches the LED off. A bitwise
operator is used to check whether the switch is closed in the if statement.
9.2.3 • The AND Bitwise Operator
The ampersand (&) represents the bitwise AND operator in C. This has nothing to do with
the address operator used to get the address of a variable as you have previously seen.
In this context the & operator is used to perform a bitwise logical operation.
The AND logical operation can be thought of in terms of an electrical circuit as shown in
Figure 9-4. The truth table for the circuit shows that only when switch 1 AND switch 2
are both on or closed, the light bulb will switch on. All other combinations of switch 1 and
switch 2 states result in the light bulb staying off. This is exactly what the C bitwise AND
operator does. In the switch_LED program, the PINC register containing the state of the
switch is ANDed with a number in order to isolate the state of the switch.
In the program, we want to test whether the switch attached to pin PC0 is open or closed
so we use a ‘mask’ value of 00000001B or 0x01 hexadecimal and bitwise AND it with the
value in the PINC register as shown in the following line of code.
if (PINC & 0x01) {
Figure 9-4 AND Circuit and Truth Table
● 167
C Programming with Arduino
The bitwise AND operation performs the AND truth table operation on all 8 bits of the
PINC register with the 8-bit mask number in parallel at the same time. Each bit in the
PINC register is ANDed with the corresponding bit in the mask number. In other words,
bit 0 of PINC is ANDed with bit 0 of the mask number, bit 1 of PINC is ANDed with bit 1 of
the mask number, etc. as shown below.
The table shows that bit 0 of PINC is ANDed with bit 0 of the mask number below it, in
other words, a bit with a value of 0 is ANDed with a bit with a value of 1. From the truth
table, 0 AND 1 results in 0. The same happens with all of the other bits in the byte as
each bit from PINC is ANDed with the bit from the mask number below it and the result
displayed below the two bits. All other bit results after the bitwise AND operation are 0,
which is derived from the truth table where 0 AND 0 is 0. In this case the switch was
open, so PINC had a value of 00000000B. In the C program, this bitwise AND operation in
the if statement evaluates to 0 or false, causing the else statement to run and switch the
LED off. The next table shows what happens when the switch is closed.
In this case, the switch is closed which results in PINC having a value of 00000001B or
0x01 hexadecimal. Now when bit 0 of PINC and bit 0 of the mask number are ANDed
together we have 1 AND 1 which results in 1 as can be seen in the truth table. All other
bits ANDed together result in 0, so the final result of ANDing all 8 bits is 00000001B or
0x01 hexadecimal. The if statement in the program now evaluates to true and the LED is
switched on.
The advantage of using the bitwise AND operation is that it does not matter what the
value of the other bits in the byte are. The bitwise AND operation isolates a single bit
which will either evaluate to true if the bit is a 1 or false if the bit is a 0. If any random
data is read in the upper bits of the PINC register, it does not matter because the mask
number together with the AND bitwise operation will always give a zero result. Only the
bit that we are interested in will be evaluated.
The table below shows that other bits set in the PINC register, which could be other
switches connected to the corresponding pins, still result in 0 when ANDed with the mask
number, effectively masking them out and isolating a single bit for evaluation.
If the switch were to be attached to PC2, we would simply change our mask number so
that bit 2 would be isolated when using the bitwise AND operation as shown in the next
table.
● 168
Chapter 9 • I/O and Memory Maps
To isolate bit 2, the mask number is changed to 00000100B or 0x04 hexadecimal. When
the switch connected to PC2 is open, the bitwise AND operation results in 0 or false.
When the switch is closed, the bitwise operation results in 0x04 which is a non-zero
number so evaluates to true. The C code to test the state of a switch on PC2, or the state
of bit 2 of PINC, is shown in the following line of code.
if (PINC & 0x04) {
This has been an introduction to one of the bitwise operators in C, we will look at other
bitwise operators in due course.
9.3 • AVR Memory Map
A memory map is a graphical depiction of the memory of an embedded system or
microcontroller and helps with visualising the memory of a system. A memory map shows
the types and sizes of memory as well as the addresses of memory. Figure 9-5 shows
the memory map of an ATmega328P as found on an Arduino Uno board and Figure 9-6
on page 170 shows the memory map of an ATmega2560 as found on the Arduino MEGA
2560.
The main difference between the ATmega328P and the ATmega2560 memory maps is
that the ATmega2560 has more memory than the ATmega328P. The ATmega2560 also
has more extended I/O registers.
Figure 9-5 ATmega328P Memory Map
● 169
C Programming with Arduino
Figure 9-6 ATmega2560 Memory Map
9.3.1 • Memory Architecture
AVRs have a Harvard architecture which means that they have separate buses for
program memory and data memory. For this reason the Flash memory and SRAM
memory of an AVR can both start at address zero or 0x0000. EEPROM memory is
organised as a separate data space and is accessed through a set of registers which also
allows its addressing to start at zero.
9.3.2 • Flash Program Memory
Flash memory, which stores the program instructions of a compiled C program, has been
arranged as 16-bit wide words because most AVR instructions have a 16-bit wide word
format. This means that a 16-bit word is found at each address location in Flash memory
instead of an 8-bit byte. The 32k bytes of Flash on an ATmega328P is arranged as 16k by
16-bit words and the 256k bytes of Flash on the ATmega2560 is arranged as 128k by 16bit words.
9.3.3 • Data Memory
Various AVR registers have been mapped into the start of the AVR data memory address
space. For this reason SRAM memory starts at 0x100 on the ATmega328P and at 0x200
on the ATmega2560. SRAM is used to store variables from a C program. I/O registers and
extended I/O registers shown in the memory maps are the registers used for accessing
various hardware devices on the AVR.
9.4 • Summary
● 170
•
The state of a switch can be found by looking at the logic level of an individual
bit in the PINx register of an AVR. A bit in the PINx register that corresponds to
the pin that the switch is connected to indicates whether the switch is open or
closed.
•
The bitwise AND operator can be used to evaluate the state of a single bit in a
register without the other bits in the register affecting the evaluation.
•
An AVR has separate program and data buses which is called a Harvard
architecture.
Chapter 9 • I/O and Memory Maps
•
Flash memory in the program area of the memory map stores the compiled C
program and is non-volatile which allows the program to start running when
power is applied to the AVR.
•
Various registers are mapped at the beginning of AVR data memory space.
•
SRAM memory in the data area of the memory map is used to store variables
from a C program allowing them to be changed while the program is running.
● 171
C Programming with Arduino
● 172
Chapter 10 • Previous C Topics Revisited
Chapter 10 • Previous C Topics Revisited
What you will do in this chapter:
•
Look at previous C topics in more detail
•
More on format specifiers, field width specifiers and escape sequences
•
Additional loops – the do-while and for loops are introduced
•
Programming the AVR USART / serial port
•
Nested loops and decisions
•
Decision making with the switch construct and conditional operator
•
Functions and pointers
•
Scope of variables and static variables
•
More on floating point variables
•
Casts in C
10.1 • Format Specifiers
Table 10-1 on page 174 shows the format specifiers in C, you have already used some
of them. The formats program makes use of some of the new format specifiers. Create
this project using the template file and view the output in the terminal program on the
PC.
Project name: formats
Project location: CH10\formats
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
UartInit();
/* characters and strings */
printf("%s %c\r\n", "This is the letter", 't');
/* integers */
printf("Ints:%d, %i, %u\r\n", -1000, -123, 958);
printf("Oct:%o, hex:%x, HEX:%X\r\n", 191, 191, 191);
/* floating point numbers */
printf("%f, %e, %E\r\n", 45.345, 45.345, 45.345);
}
while(1) {
}
Program Output
This is the letter t
Ints:-1000, -123, 958
Oct:277, hex:bf, HEX:BF
45.345001, 4.534500e+01, 4.53400E+01
● 173
C Programming with Arduino
%s and %c are used to print a string and character respectively. You have already seen %d
for printing integer decimal numbers, %i does the same thing and %u works with unsigned
integers. %x and %X are for printing hexadecimal numbers. %o prints out a number in octal
format. Octal is another number system like binary or hexadecimal, but uses base 8. %f
is familiar to you and is used to print a floating point number. %e and %E print a number in
exponential notation, which is a notation used by scientists and engineers.
Table 10-1 C Format Specifiers
Format Specifier
Data Type
Print Format
%d or %i
int
Decimal number
%u
unsigned int
Unsigned decimal number
%x
unsigned int
Lowercase hexadecimal
%X
unsigned int
Uppercase hexadecimal
%o
unsigned int
Octal unsigned integer
%c
char or int
Character
%s
string
A string of characters
%f
float
Floating point number
%e
float
Lowercase exponential notation
%E
float
Uppercase exponential notation
10.2 • Field Width Specifiers Revisited
We have already seen field width specifiers used with floating point numbers to set the
number of digits to print after the decimal point. The number of digits can also be set before
the decimal point, or in the case of decimal and hexadecimal numbers, set the number of
place holders to the left of the number. As an embedded programmer, you may wish to use
this to show the width of a number when using hexadecimal numbers, or to align numbers.
For example, the number 0x5 is a 3 bit number (1012), but in a microcontroller or computer,
memory is always displayed in multiples of bytes, so we may wish to display this number with
padding zeros to represent all 8 bits – 0x05 (0000 01012). The next program, padding, shows
how to pad different numbers with zeros or spaces. Create the project using the template file.
Project name: padding
Project location: CH10\padding
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
UartInit();
// a 3 bit hex number padded to 8 bits
printf("0x%X\n", 0x5);
// unpadded
printf("0x%2X\n", 0x5);
// pad with spaces
printf("0x%02X\n\n", 0x5);
// pad with 0
// a 3 bit hex number padded to 16 bits
printf("0x%4X\n", 0x5);
// pad with spaces
printf("0x%04X\n\n", 0x5);
// pad with 0
● 174
Chapter 10 • Previous C Topics Revisited
// decimal number padded
printf("%d\n", 123);
// unpadded
printf("%5d\n", 123);
// padded with spaces
printf("%05d\n\n", 123); // padded with 0
// float padded
printf("%f\n", 34.218); //
printf("%10f\n", 34.218); //
printf("%10.4f\n", 34.218); //
printf("%010.4f\n\n", 34.218); //
}
unpadded
padded with spaces
pad = space, 4 nums after .
pad = 0, 4 nums after .
while(1) {
}
Program Output
0x5
0x 5
0x05
0x
5
0x0005
123
123
00123
34.217999
34.217999
34.2180
00034.2180
Run the program and note the following:
1.
A number placed in the middle of the format specifier (directly after the %) is
the field width specifier (which is now part of the format specifier).
2.
A field width specifier will pad a number with spaces unless it is preceded with
0, in which case it will pad the number with 0s.
3.
The value of the field width specifier is the total width of the number printed.
So if the number is 23 and the field width specifier is 1 or 2, you will not see
any padding. As soon as a width of 3 or more is used, the padding will be seen
as the number is forced to a length of 3 digits instead of 2 in this example.
4.
When using field width specifiers with floating point numbers, the number in
front of the dot of the field width specifier specifies the entire length of the
number printed including the decimal point and all numbers in front of and
behind it. The number after the dot in the field width specifier specifies the
number of digits after the decimal point in the printed number.
Figure 10-1 on page 176 shows the above points diagrammatically.
Something to note about floating point numbers is that they do not always print out
exactly as they are written in the C program. From the previous examples we can see
that 45.345 may be stored as 45.345001 (from the formats program) and 34.218 may
be stored as 34.217999. This has to do with how floating point numbers are implemented
and stored in memory in C. For this reason, floating point numbers should never be
● 175
C Programming with Arduino
compared for exact matches using an equality operator.
Figure 10-1 Field Width Specifiers in Decimal and Floating Point Numbers
10.3 • Escape Sequences
You have already seen the newline and carriage return escape sequences. Table 10-2
shows the other possible escape sequences in C.
Table 10-2 C Escape Sequences
Escape Sequence
Meaning
\a
alert (bell character)
\b
backspace
\f
formfeed
\n
newline
\r
carriage return
\t
horizontal tab
\v
vertical tab
\\
backslash
\?
question mark
\'
single quote
\"
double quote
\ooo
octal number
\xhh
hexadecimal number
An escape sequence always begins with a backslash. This gives the backslash character
special meaning in C. To print the backslash character, we therefore need to use an
escape sequence. This is the double backslash (\\) shown in the table.
The same applies to single and double quotes. These characters have special meaning in
C, so to print one of these characters out, we need an escape sequence. For example the
following statement: printf("I said, "Hello"");
would cause a compile error because the compiler sees "I said, " as a string. Hello is
● 176
Chapter 10 • Previous C Topics Revisited
not part of the string, but falls outside of it. We need to use escape sequences to be able
to print this text: printf("I said, \"Hello\"");
Characters can also be inserted directly into a string by using the hexadecimal number
escape sequence. This will insert the equivalent ASCII character from the ASCII table in
place of the hexadecimal number. The hexadecimal number escape sequence can be used
to print characters that you would not be able to type into your C source code, such as
the graphical characters from the ASCII table.
The escape project demonstrates the use of some of the new escape sequences. The
program shows two ways of inserting quotes into a string, how to insert characters into a
string from the ASCII table and how to insert graphical characters into a string from the
ASCII table.
Before running this program, set up the Tera Term font as shown in Figure 10-2 on page
178 in order to show the graphical characters properly. To open the Font selection dialog
box, click Setup → Font... on the top menu. Even though the Terminal font appears to
be selected, click a different font and then click the Terminal font again to select it. Click
OK when done. Changes can be saved by using Setup → Save Setup... from the top
menu of Tera Term so that the font does not have to be manually selected again.
Project name: escape
Project location: CH10\escape
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
UartInit();
/* printing quotes in a string */
printf("I said, %cHello%c\r\n", '"', '"');
printf("I said, \"Hello\"\r\n");
/* using hex number escape sequences */
printf("%c%c%c\r\n", 'A', 'B', 'C');
printf("\x41\x42\x43\r\n");
/* using hex numbers to print graphical characters */
printf("\xC9\xCD\xCD\xCD\xCD\xCD\xBB\r\n");
printf("\xBA AVR \xBA\r\n");
printf("\xC8\xCD\xCD\xCD\xCD\xCD\xBC\r\n");
}
while(1) {
}
Program Output
See Figure 10-2
The program first prints out quotes using format specifiers and character constants, the
character constants being the double quotes (") placed between single quotes. The same
string is then printed out using escape sequences which directly inserts the quotes into
the string, no need for format specifiers.
An example is then given of printing out the characters A, B and C first using character
constants and then using the hexadecimal number escape sequence. The hexadecimal
number is used to print out the same characters by using the correct number from the
● 177
C Programming with Arduino
ASCII table.
Finally the program prints out some graphical characters from the ASCII table that you
would not be able to type into your C source code. The output of the program is shown in
Figure 10-2. Refer to Appendix A for the ASCII table.
The correct font set must be selected in the terminal program used with the above
program in order to show the graphical characters correctly that display a box around the
letters AVR. Many font sets in Windows do not contain the graphical characters from the
extended part of the ASCII table and will print out incorrect characters.
Figure 10-2 Setting up the Font in Tera Term
10.4 • Loops
We have only seen one type of loop in C so far, the while loop. We will look at two more
C loops, the do – while loop and the for loop, but let’s first look at the break and
continue statements used in a while loop. These statements can be used in the other
types of loops as well.
10.4.1 • A while Loop that uses break and continue
The brk_cont project demonstrates how the break and continue statements work.
Project name: brk_cont
Project location: CH10\brk_cont
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
int count = 0;
UartInit();
while (count < 10) {
count++;
● 178
Chapter 10 • Previous C Topics Revisited
if (count == 5)
break;
printf("Count 1 = %d\r\n", count);
}
count = 0;
while (count < 10) {
count++;
if (count == 5)
continue;
printf("Count 2 = %d\r\n", count);
}
}
while (1);
Program Output
Count
Count
Count
Count
Count
Count
Count
Count
Count
Count
Count
Count
Count
1
1
1
1
2
2
2
2
2
2
2
2
2
=
=
=
=
=
=
=
=
=
=
=
=
=
1
2
3
4
1
2
3
4
6
7
8
9
10
The break Statement
In this program, the break statement is demonstrated first. In the first while loop,
a count is incremented at each pass through the loop and the result is printed to the
serial terminal. When the value of the count reaches 5, the break statement is run. This
statement breaks out of the loop altogether and will no longer run any of the code in the
loop or evaluate the loop expression again. Program flow continues below this while loop.
The continue Statement
The continue statement is used in the second while loop. In this loop, the continue
statement is run when the count reaches a value of 5. The continue statement causes
program flow to continue at the top of the loop and skip all of the statements below it. In
this program, printing out of the count value to the serial terminal is skipped when the
count value is equal to 5. This is because the statement below the continue statement is
not run at all, instead the loop expression is evaluated and then the count incremented at
the top of the loop.
This program also shows that an empty while(1) loop can be written without the opening
and closing braces and is simply terminated with a semicolon. The following two lines of
code do exactly the same thing by creating an empty infinite loop that stops program
execution continuing in uninitialised memory that contains no program instructions to
run.
while(1) {
}
while(1);
● 179
C Programming with Arduino
10.4.2 • The do – while Loop
The do – while loop is similar to the while loop, except that it is always run once, no
matter what the loop expression evaluates to. The reason that the loop runs at least once
is because the loop expression is evaluated after each pass through the loop. You can use
this kind of loop every time that you want the code in the loop to be run at least once, no
matter what the loop expression result is. The do_while program shows how a
do – while loop works.
Project name: do_while
Project location: CH10\do_while
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
int count = 0;
UartInit();
do {
count++;
printf("Count: %d\r\n", count);
} while (count <= 10);
}
while (1);
Program Output
Count:
Count:
Count:
Count:
Count:
Count:
Count:
Count:
Count:
Count:
Count:
1
2
3
4
5
6
7
8
9
10
11
The loop consists of two keywords, do and while. Note also that the while statement in
this loop is terminated with a semicolon, unlike in the normal while loop. The loop will
always be entered into and the statements run at least once. The loop expression is then
evaluated and if the result is true, the loop will be run again. Figure 10-3 on page 181
shows how the do – while loop works.
When the do_while program is run, the count that is displayed is from 1 to 11 and
not 1 to 10. The reason is that when a count of 10 is reached, the loop expression still
evaluates to true. The loop is therefore run another time, incrementing the count to 11.
When the loop expression is evaluated with a count value of 11, the result is false and the
loop is exited.
● 180
Chapter 10 • Previous C Topics Revisited
Figure 10-3 The do – while Loop
10.4.3 • The for Loop
The for loop is most often used in C programs for counting or doing something a certain
number of times. The for_loop program that follows is similar to the water program of
chapter four, except that it uses a for loop instead of a while loop.
Project name: for_loop
Project location: CH10\for_loop
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
int level;
UartInit();
for (level = 0; level <= 10; level++) {
printf("Level = %d litres\r\n", level);
}
printf("Finished");
}
while(1) {
}
Program Output
Level = 0 litres
Level = 1 litres
Level = 2 litres
Level = 3 litres
Level = 4 litres
Level = 5 litres
Level = 6 litres
Level = 7 litres
Level = 8 litres
Level = 9 litres
Level = 10 litres
Finished
● 181
C Programming with Arduino
Figure 10-4 illustrates how the for loop works.
Figure 10-4 The for Loop
The for loop contains three expressions at the top of the loop body, namely, the
initialisation expression, the control expression and the adjustment expression.
The initialisation expression is only evaluated once, when the for loop is first run in order
to perform the necessary initialisation of the variable that will be used in the loop.
The control expression is evaluated every time that the loop is run, much like the loop
expression in the while loop. If this expression evaluates to true, the statements in the
body of the loop will be run. If it evaluates to false, the loop will be exited.
The adjustment expression is executed after each pass through the loop and before the
control expression is evaluated again. This expression is used to change the value of the
variable that will result in the loop being exited.
Here is how the for loop operates in the for_loop program:
1.
Initialise the level variable to zero (level = 0)
2.
Evaluate the control expression (level <= 10)
3.
Run the statements in the loop body
4.
Increment the level variable by one (level++)
5.
Repeat steps 2 to 4 until step 2 evaluates to false, then exit the loop
10.5 • The Problem with printf() in Embedded Systems
The printf() function is very convenient to use when learning the C language and
allows text to be easily printed to a terminal program on a PC. In practical embedded
systems, printf() is seldom if ever used because it uses a lot of memory which is
a limited resource on microcontrollers, especially on smaller microcontrollers. One
solution to this problem is to write functions that use the serial port directly to send and
receive characters or strings of text. Although custom serial port functions will have less
formatting options for text, they will use much less memory.
On an embedded system printf() needs to be configured to tell it where to print to.
There is no standard output device on an embedded system and the available device
could be a serial port, LCD, or other screen, or there may be no standard output at all.
Part of what the template file used with this book does is set up the serial / USB port on
the Arduino as the output for the printf() function.
In this section we look at programming the serial port using the set of registers available
for the serial port and write some basic functions for using the serial port.
10.5.1 • Programming the Serial Port
The hardware device inside an AVR microcontroller used to control a serial port is called
a USART (Universal Synchronous and Asynchronous serial Receiver and Transmitter).
A USART converts a byte into a serial stream of bits for transmitting over a serial link
● 182
Chapter 10 • Previous C Topics Revisited
and converts serial data received from a serial link to bytes that can be read by the
microcontroller.
On both the Uno and MEGA, USART0 is wired to a second AVR chip in a small square
surface mount package on the Arduino board near the USB connector as shown in
Figure 10-5. The second AVR microcontroller is configured as a virtual COM port when
plugged into a PC. Serial port data sent and received from the main AVR microcontroller
is routed from the on-chip USART to the second AVR microcontroller and USB port. In
this way sending and receiving serial data from USART0 is just like sending and receiving
data to and from a PC using one of the older RS-232 serial ports that used a 9-pin D-type
connector.
There is one USART on the ATmega328P, USART0, and four USARTs on the ATmega2560,
USART0, USART1, USART2 and USART3. Because USART0 is present on both chips, the
same code for programming the serial port can be used on the Uno and MEGA.
Before using the USART as a serial port, it must be configured. Basic configuring of
USART0 as a serial port requires data to be written to two registers to set up the baud
rate of the serial port, which is the speed at which the serial port communicates. The
transmit and receive pins of the USART must be set up as such to configure these pins
with their alternate function instead of as general purpose input or output pins. Finally
the communication parameters for the serial port must be configured, such as number of
data bits, number of stop bits and parity bits.
In the program that follows, USART0 will be configured as follows:
•
Baud rate 9600
•
8-bit data width
•
1 stop bit
•
No parity
When a program such as Tera Term is connected to the Arduino virtual COM port on a PC,
it must be set up with matching communication parameters.
Figure 10-5 A Second AVR Adds a USB Port to the Arduino
After the USART is initialised, further registers are used to check whether the USART is
ready to transmit or if a byte has been received by the USART. Other registers are then
used to send a byte or to read an incoming byte.
The following registers are used for setting up USART0 for transmitting and receiving:
•
UBRR0H and UBRR0L – sets the baud rate
•
UCSR0B – enable USART0 transmit and receive pins
•
UCSR0C – USART mode and communication parameters
● 183
C Programming with Arduino
•
UCSR0A – used to check if a byte was received and if the USART is ready to
transmit
•
UDR0 – writing to this register sends a byte, reading from this register gets a
received byte
10.5.2 • Writing Serial Port Functions
Three serial port functions can be found in the serial_port project code listing below.
Functions for initialising USART0 and then transmitting and receiving data using USART0
can be found in the code. The project was created without the use of the template file. If
the template file is used to create this project it will not be able to be built properly and
Atmel Studio will display an error.
Output from the program is displayed in the terminal program on a PC. The program
sends two bytes out of the serial port and then echoes any characters typed into the
terminal program back to the terminal program.
Project name: serial_port
Project location: CH10\serial_port
main.c
#define F_CPU 16000000UL
#define BAUD 9600
#define BAUD_TOL 2
#include <avr/io.h>
#include <util/setbaud.h>
void UartInit(void);
void UartTxByte(char data);
int UartRxByte(void);
int main(void)
{
int rx_data;
UartInit();
UartTxByte('H');
UartTxByte('i');
}
while(1) {
rx_data = UartRxByte();
if (rx_data > -1) {
UartTxByte(rx_data);
}
}
void UartInit(void)
{
// set UART0 baud rate
UBRR0H = UBRRH_VALUE;
UBRR0L = UBRRL_VALUE;
// dedicate pins to USART0 for transmit and receive
UCSR0B = 0x18;
// 8-bit, 1 stop bit, no parity, asynchronous UART
UCSR0C = 0x06;
}
● 184
Chapter 10 • Previous C Topics Revisited
void UartTxByte(char data)
{
while(!(UCSR0A & 0x20));
UDR0 = data;
}
// wait until ready to tx
// transmit byte
int UartRxByte(void)
{
int data;
if (UCSR0A & 0x80) {
data = UDR0;
}
else {
data = -1;
}
}
// check if byte received
// get received byte
// byte not received
return data;
Example Program Output
Hi
abcd
123
Hello
The example program output shows that the program initially sends the letters "Hi" out
of the serial port. The Enter key was then pressed followed by Ctrl + Enter to move the
cursor to the next line in the terminal window by sending a carriage return and newline
character from the keyboard. Various characters were then typed into the terminal
program that were echoed back to the terminal program by the Arduino. Each line was
followed by Enter and Ctrl + Enter.
In the body of main(), three functions are used to control the serial port:
•
UartInit() – initialises the serial port to the desired configuration
•
UartTxByte() – waits until the USART is ready to transmit and then sends a
byte
•
UartRxByte() – checks if a byte was received and returns the received byte if
available
In the while(1) loop of the program, UartRxByte() is continually called and the value
that it returns is placed in the variable rx_data. If no new byte was received by the
USART, the function will return -1 and the if statement will evaluate to false. When a
byte is received by the USART, the function will return the received byte which will be a
value that is greater than -1 and the if statement will evaluate to true. Inside the body
of the if statement, UartTxByte() is called to transmit the received byte back out of the
serial port, resulting in data typed into Tera Term being received back in the Tera Term
window.
● 185
C Programming with Arduino
Figure 10-6 Registers Used to Control USART0
Setting the Baud Rate
UBRR0H = UBRRH_VALUE;
UBRR0L = UBRRL_VALUE;
In UartInit(), registers UBRR0H and UBRR0L are used to set the baud rate of the
USART. The AVR library provides a convenient way of setting the baud rate without having
to calculate the values to assign to these two registers. By defining values for
F_CPU, BAUD and BAUD_TOL at the top of the C file, the values to pass to the two baud
registers are automatically calculated and made available in UBRRH_VALUE and
UBRRL_VALUE which need only be assigned to the two baud registers to set the desired
baud rate. The desired baud rate is set by the value assigned to BAUD which is set to
9600 in this program.
Configuring the Transmit (Tx) and Receive (Rx) Pins
UCSR0B = 0x18;
On both the Uno and MEGA, Arduino pin number 0 is the serial port receive (Rx) pin and
Arduino pin number 1 is the serial port transmit (Tx) pin. On the Arduino Uno the serial
port Rx pin maps to port pin PD0 and the Tx pin maps to port pin PD1. On the MEGA the
Rx pin maps to port pin PE0 and Tx maps to port pin PE1. These pins are configured as
USART0 Tx and Rx pins in UCSR0B by setting the RXEN0 and TXEN0 bits as shown in
Figure 10-6 at the top. Setting these two bits to logic 1 values and the rest of the bits in
the register to logic 0 values is done by writing the value 0x18 to this register as shown in
the figure.
Setting Serial Communication Parameters
UCSR0C = 0x06;
UCSR0C (middle register in Figure 10-6) is used to set up the remaining serial
communication parameters. The upper two bits of this register, bits UMSEL01 and
UMSEL00, are cleared to logic 0 values to set the USART up in asynchronous USART
mode, which is required for the type of serial port communication that we are using it
for. Bits UPM01 and UPM00 are both cleared which sets parity to none. USBS0 is cleared
which makes the number of stop bits 1 bit. Clearing bit UCSZ02 in the UCSR0B register
(top register in the figure) and setting bits UCSZ01 and UCSZ00 in the UCSR0C register
(middle register in the figure) configures the data width of the USART to 8 bits. The least
significant bit in UCSR0C is not used in asynchronous mode and is set to 0.
● 186
Chapter 10 • Previous C Topics Revisited
Transmitting a Byte
void UartTxByte(char data)
{
while(!(UCSR0A & 0x20));
UDR0 = data;
}
Before a byte can be transmitted, the UDRE0 bit in UCSR0A (bottom register in
Figure 10-6 on page 186) is checked to see if it is set. If this bit is set, it means that
the USART is ready to transmit and a byte can be written to UDR0 which will then be sent
serially on the Tx pin of the USART.
The UDRE0 bit is checked in a while loop that will wait until the bit is set before the
line of code below it is run which transmits the data. The bitwise AND operator is used
on the register to test if the bit is set with the expression (UCSR0A & 0x20). When
the bit is set, the expression (UCSR0A & 0x20) will evaluate to true, which will cause
program execution to be stuck in the loop. If the USART is not ready for transmitting, this
expression will evaluate to false and the loop will be exited. This is exactly the opposite of
what we want to happen. The NOT operator (!) is used to invert the logic in order to get
the program to behave as we would like.
The NOT operator applied to the result of any logic evaluation will invert the result. In
other words if an expression evaluates to true and the NOT operator is applied to it, the
result will change to false. If an expression evaluates to false, the NOT operator will invert
it to be true.
Applied to the above code, when the UDRE0 bit is 0, then the USART is not available
to transmit and we want to wait in the while loop. The expression (UCSR0A & 0x20)
evaluates to false, but after the NOT operation is applied, it evaluates to true and we stay
in the loop as desired. As soon as the USART is available to transmit, it sets the UDRE0
bit and (UCSR0A & 0x20) now evaluates to true. This is when we want to exit the loop
and send the byte. The NOT operator inverts the evaluation of true to false, which allows
program flow to exit the loop and send the byte.
Receiving a Byte
int UartRxByte(void)
{
int data;
if (UCSR0A & 0x80) {
data = UDR0;
}
else {
data = -1;
}
}
return data;
UartRxByte() reads a single byte from the USART if a byte is available i.e. if a byte
has been received over the serial link. The return type of this function is int which is
bigger than the 8-bit byte width of UDR0, the register that contains a byte that has been
received. Because the return type of this function is bigger (16 bits wide) than the data
being received, it can return -1 to indicate that no new data was received. Any data byte
received will be a positive number from 0 to 255.
The if statement in UartRxByte() uses the bitwise AND operator to test whether the
● 187
C Programming with Arduino
RXC0 bit is set in UCSR0A. If this bit is set it means that a new byte was received over
the serial link and the received byte can be read from UDR0. If the RXC0 bit is not set, it
means that a new byte was not received since the last time that this function was run. In
this case, the variable data is assigned a value of -1 which is returned by the function.
10.6 • Nested Loops and Decisions
Nesting in programming refers to putting one thing inside another. In the case of loops,
we can put one loop inside another loop. With decisions, we can have a decision within a
decision.
10.6.1 • Nested Loops
The nest_loop program below shows one for loop inside another for loop. Create this
project using the template file.
The second for loop is said to be nested in the first for loop. The first loop counts from
1 to 3. Every time that the loop executes the statements in the loop’s body, it runs the
nested loop as well. The nested loop counts through the ASCII characters from A to D.
Because the letters in the ASCII table are sequential, we can move from letter to letter by
incrementing each character by one.
The nested loop could also use hexadecimal numbers or decimal numbers to achieve
the same result. Each of the following for loops produce exactly the same results in this
program:
for (ch = 'A'; ch <= 'E'; ch++)
for (ch = 0x41; ch <= 0x45; ch++)
for (ch = 65; ch <= 69; ch++)
Every time that the main loop is run, the nested loop is run to completion. For every
increment of 1 in the main loop, the nested loop counts through the characters A to D.
After the nested loop is run, four "-" signs are printed out when the statement below the
nested loop is run.
Any kind of loop can be nested in any other kind of loop. You can nest a for loop in a
while loop, a while loop in a for loop, etc.
Project name: nest_loop
Project location: CH10\nest_loop
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
int count;
char ch;
UartInit();
for (count = 1; count <= 3; count++) {
for (ch = 'A'; ch < 'E'; ch++) {
printf("%d, %c\r\n", count, ch);
}
● 188
Chapter 10 • Previous C Topics Revisited
}
}
printf("----\r\n");
while (1);
Program Output
1, A
1, B
1, C
1, D
---2, A
2, B
2, C
2, D
---3, A
3, B
3, C
3, D
----
10.6.2 • Nesting Decisions
Decisions can also be nested as the code in the nest_if project below shows. This project
must be created using the template file.
Use Tera Term to send keyboard characters to this program over the USB / serial port.
Sending ‘1’ or ‘2’ will display valid menu items in the terminal window. Any other
character sent to the program will result in a message displaying the invalid character.
Project name: nest_if
Project location: CH10\nest_if
main.c
#include <avr/io.h>
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
char rx_data;
UartInit();
while (1) {
if (UCSR0A & 0x80) {
// serial data byte received
rx_data = UDR0;
// get the serial data byte
if (rx_data == '1') {
printf("Menu item 1 selected\r\n");
}
else if (rx_data == '2') {
printf("Menu item 2 selected\r\n");
}
else {
printf("%c invalid selection\r\n", rx_data);
● 189
C Programming with Arduino
}
}
}
}
Example Program Output
Menu item 1 selected
Menu item 2 selected
3 invalid selection
This program uses the serial port USART registers as done in the serial_port project, but
this time it is not necessary to include a separate function to initialise the USART because
this program is created from the template files and UartInit() is included with the
template and called at the beginning of the program which initialises the USART.
Figure 10-7 Nested Decisions
Inside the while(1) loop, the first if statement continually checks if a character has
been received on the serial port by checking the RXC0 bit in UCSR0A. When a character is
received, it is read from UDR0 and stored in the rx_data variable. A nested if statement
is used to check if the received character is a valid menu option. The use of indenting
helps to show which else belongs to which if statement as shown in Figure 10-7.
10.7 • Decision Making with the switch Statement
The menu program demonstrates the use of the switch statement which is similar to
using the if – else if construct in C. Create this project using the template file.
Project name: menu
Project location: CH10\menu
main.c
#include <avr/io.h>
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
char rx_data;
● 190
Chapter 10 • Previous C Topics Revisited
UartInit();
while (1) {
if (UCSR0A & 0x80) {
rx_data = UDR0;
switch (rx_data) {
case '1':
printf("Menu item 1.\r\n");
break;
case '2':
printf("Menu item 2.\r\n");
break;
case '3':
printf("Menu item 3.\r\n");
break;
case '4':
printf("Menu item 4.\r\n");
break;
}
}
}
}
default:
printf("Invalid selection.\r\n");
break;
Example Program Output
Menu item 1.
Menu item 2.
Invalid selection.
Menu item 3.
Menu item 4.
Invalid selection.
The example program output shows the output from the program when the characters
12e345 are sent to it.
When this program is run, pressing keys 1 to 4 in the terminal window will select various
menu items. If anything else is pressed, a default message is displayed to say that the
selection was invalid.
● 191
C Programming with Arduino
Figure 10-8 The switch Statement
Four C keywords are associated with the switch construct, namely switch, case,
default and break. The switch statement uses a set of parentheses that contain a
variable or expression that is to be selected for in the body of the switch statement.
Inside the body of the switch statement are a number of case statements. The switch
variable or expression is checked to see if it matches one of the case statements. If it
does, the statements under the case statement are run in the order that they appear.
To stop running any further unwanted statements, the break keyword is used to
break out of the body of the switch statement. If break was not used, all of the other
statements below the case would run.
The statements under the default keyword will be run if no match is found at any case
statement.
The switch statement does the same as the following code, but using switch is easier to
read and more flexible:
if (rx_data == '1') {
printf("Menu item 1.\r\n");
}
else if (rx_data == '2') {
printf("Menu item 2.\r\n");
}
else if (rx_data == '3') {
printf("Menu item 3.\r\n");
}
else if (rx_data == '4') {
printf("Menu item 4.\r\n");
}
else {
printf("Invalid selection.\r\n");
}
The default statement can be left out if desired and is the equivalent of leaving off the
else statement in the code example above. Figure 10-8 shows the details of the switch
statement. In the figure, only the case of rx_data containing a value of 1 is shown, but
each of the other cases will behave the same way – e.g. if rx_data contains a value of 2,
● 192
Chapter 10 • Previous C Topics Revisited
the code under case ‘2’ will run and then the break statement will exit the switch body.
10.8 • The Conditional Operator
Another operator used for making decisions is known as the conditional operator. The
sw_condition program demonstrates its use. For this project connect a switch to pin PC0
as shown in Figure 9-3 on page 166 and create the project using the template file.
Output from the program is displayed in Tera Term and is continually sent out of the serial
port.
Project name: sw_condition
Project location: CH10\sw_condition
main.c
#define
#include
#include
#include
#include
F_CPU 16000000UL
<avr/io.h>
<util/delay.h>
<stdio.h>
"stdio_setup.h"
int main(void)
{
unsigned char sw_state = 0;
UartInit();
DDRC = 0x00;
}
// port C pins set as outputs
while(1) {
sw_state = (PINC & 0x01) ? 1 : 2;
printf("Switch state: %d\r\n", sw_state);
_delay_ms(200);
}
Example Program Output
Switch
Switch
Switch
Switch
Switch
Switch
Switch
Switch
...
state:
state:
state:
state:
state:
state:
state:
state:
2
2
2
1
1
1
2
2
The program monitors the push button switch and will display a message containing
either 1 or 2, depending on if the switch is closed or not. The conditional operator is used
to choose the number to send to the terminal program, depending on the state of the
switch. Figure 10-9 on page 194 shows how the conditional operator works.
The conditional operator consists of a condition that will evaluate to either true or false
as we have previously seen in C. Should the result of the condition be true, the first
expression will be the result of the operation. In the example program, the sw_state
variable will be assigned a value of 1 when the condition is true, i.e. in this program when
the switch is pressed or closed.
● 193
C Programming with Arduino
Should the condition evaluate to false, the second expression will be the result and will
therefore be substituted into the sw_state variable. The sw_state variable will then
contain a value of 2 in this example.
Figure 10-9 The Conditional Operator
10.9 • Functions and Pointers
When you pass a variable to a function, the function is sent a copy of the variable and
operates on this local copy of the variable. If the function modifies the variable, the
original variable that was passed to it remains unmodified.
In C the address of a variable can be passed to a function. The function will then be able
to modify the value of the variable as it has the address of the variable and not a copy of
the variable’s contents.
10.9.1 • Passing an Address to a Function
The next project called point_func shows how to pass the address of a variable to a
function. Create this project from the template file and view the output in the terminal
window.
Project name: point_func
Project location: CH10\point_func
main.c
#include <stdio.h>
#include "stdio_setup.h"
void func1(int num);
void func2(int *pnum);
int main(void)
{
int number = 123;
UartInit();
printf("Number is %d\r\n", number);
func1(number);
printf("Number is %d\r\n", number);
● 194
Chapter 10 • Previous C Topics Revisited
func2(&number);
printf("Number is %d\r\n", number);
}
while(1);
void func1(int num)
{
num = 567;
}
printf("Func1 num %d\r\n", num);
void func2(int *pnum)
{
*pnum = 987;
}
printf("Func2 pnum %d\r\n", *pnum);
Program Output
Number is 123
Func1 num 567
Number is 123
Func2 pnum 987
Number is 987
The variable number is first printed out and displays the value that it was initialised to
(123). This variable is then passed to the function func1() which changes the value
of the variable and prints the result out (567). When the number variable was passed
to func1(), a copy of the variables contents was made to a "local" variable in func1()
called num. This local variable was modified to contain the number 567 and left the
variable that was passed to it unaffected. This is confirmed when, after returning from
func1(), the value of number is printed out again and remains unchanged (123).
Function func2() is passed the address of the variable number (&number). This address
is stored in the pnum pointer variable which is local to func2(). Inside this function, the
address variable is dereferenced (*pnum) in order to operate on the contents of the
variable that it is pointing to. It is of course pointing to the variable number and not to a
local copy of number. The following line of C code in func2() means "assign the value of
987 to the memory location that pnum is pointing to".
*pnum = 987;
pnum contains the address of number and is therefore pointing to it. The value of the
variable that pnum is pointing to is then printed out inside func2() (987).
After returning from func2(), the value of number is printed out again (987). It is now
987 and not 123 any more, because we modified the contents of this variable by passing
its address to func2().
10.9.2 • Returning More Than One Value from a Function
Pointers enable us to return more than one value from a function. The return2 program
uses two pointers to return two values from a function.
● 195
C Programming with Arduino
Project name: return2
Project location: CH10\return2
main.c
#include <stdio.h>
#include "stdio_setup.h"
void retfunc(int *val1, int *val2, int input);
int main(void)
{
int num1, num2;
UartInit();
retfunc (&num1, &num2, 34);
printf("34 squared = %d\r\n", num1);
printf("34 * 10 = %d\r\n", num2);
}
while (1);
void retfunc(int *val1, int *val2, int input)
{
*val1 = input * input;
*val2 = input * 10;
}
Program Output
34 squared = 1156
34 * 10 = 340
The retfunc() function in this program takes two pointer arguments and one integer
argument. The pointer arguments are used to store the two output values from the
function into the two variables that they point to.
When retfunc() is called, it is passed the address of the variable num1 and the address
of variable num2. The constant 34 is also passed to the function in the function variable
input. The function then calculates the square of the variable input (input multiplied by
itself) and saves it in the variable that pointer val1 is pointing to i.e. num1.
Next, retfunc() calculates input multiplied by 10 and stores it in the variable that
pointer val2 is pointing to, i.e. num2. The function has thus returned two values which
it would not normally be able to do. After returning to main(), the calculated values are
found in the variables num1 and num2 and are printed out.
10.10 • Variables and Scope
10.10.1 • Local Variables
Variables defined in a function have what is known as "function scope". A variable defined
in main() can only be used and "seen" inside main(). A function can only "see" the
variables defined inside it and does not know about variables that are defined inside any
other function.
● 196
Chapter 10 • Previous C Topics Revisited
If you try to use a variable inside a function that was defined in another function, the
compiler will display an error. A variable can be defined inside a function with the same
name as a variable that has already been defined in another function because of the
scope of the variables being limited to the functions in which they are defined. Variables
that are defined inside a function are known as local variables as they are local to the
function.
10.10.2 • Global Variables
Global variables have "file scope", meaning that they can be "seen" and used by every
function in a file – the C source file that they are defined in. A global variable is also
known as an external variable. The glob_var program uses a global variable to pass
information between functions.
Project name: glob_var
Project location: CH10\glob_var
main.c
#include <stdio.h>
#include "stdio_setup.h"
int glob_count = 0;
void AddOne(void);
void Delay(void);
int main(void)
{
UartInit();
}
while (1) {
printf("Count is %d\r\n", glob_count);
AddOne();
Delay();
}
void AddOne(void)
{
glob_count++;
}
void Delay(void)
{
volatile long del = 1000000;
while (del--);
}
Sample Program Output
Count
Count
Count
Count
...
is
is
is
is
0
1
2
3
In this program, the variable glob_count is defined as a global variable by defining
it outside of main(). All functions inside the file in which it exists have access to it. In
● 197
C Programming with Arduino
the program, glob_count is incremented in the AddOne() function and displayed in the
main() function. Both these functions have therefore accessed this global variable.
Global variables can be helpful, but it is not considered good programming practice to use
too many global variables as one can loose track of which functions are changing these
variables in a big program.
Global variables must have unique names even if they occur in different files of a
program. If the linker finds two global variables with the same name that were defined
in two different files of the same project, it will not be able to link the project and will
display an error message.
10.11 • Static Variables
Variables that are declared within a function, i.e. local variables, are destroyed when the
function returns. The memory that local variables use when the function is running is
freed up for use by other parts of the program when the function returns. The static
keyword can be used to prevent local variables from being destroyed as the static_var
program demonstrates.
Project name: static_var
Project location: CH10\static_var
main.c
#define
#include
#include
#include
F_CPU 16000000UL
<stdio.h>
"stdio_setup.h"
<util/delay.h>
int Count(void);
int StaticCount(void);
int main(void)
{
UartInit();
}
while (1) {
printf("count %d, static count %d\r\n", Count(), StaticCount());
_delay_ms(1000);
}
int Count(void)
{
int count = 0;
return count++;
}
int StaticCount(void)
{
static int count = 0;
return count++;
}
● 198
Chapter 10 • Previous C Topics Revisited
Sample Program Output
count
count
count
count
count
...
0,
0,
0,
0,
0,
static
static
static
static
static
count
count
count
count
count
0
1
2
3
4
This program calls two functions that perform identical actions of incrementing a local
variable and returning the result. The only difference between the functions is that one
uses a static variable and the other does not. Note that the post increment operator is
used in these functions, so the count is first returned and then incremented.
The non-static variable is always initialised to 0 when the function is called and it
effectively "forgets" the value of this variable between calls. The result is that when the
returned value is printed, a 0 is always displayed.
The StaticCount() function uses a static local variable by preceding the variable
definition with the static keyword. The variable is initialised to 0 the first time that
the function is called and thereafter "remembers" the value that the variable contains
between function calls. The result is that every time this function is called in the program,
the next sequential count value is returned because this variable is never destroyed.
10.12 • Floating Point Data Types
We covered the integer data types in chapter 7 and showed these data types in Table 7-3
on page 138. We will now take a look at the floating point data types. Floating point
numbers are used to store numbers that have a decimal point, and approximate what is
know as real numbers in mathematics.
Table 10-3 shows the three floating point data types in C with their approximate ranges.
Floating point numbers are signed numbers and always have one bit of the number
dedicated to represent the sign (positive or negative) of the number. There is no such
thing as an unsigned floating point number.
Table 10-3 Floating Point Data Types
Type
Size
Range
Precision
float
4 bytes
1.2E-38 to 3.4E+38
6 digits
double
8 bytes
2.3E-308 to 1.7E+308
15 digits
8-bit AVR double
Same as 8-bit AVR float
long double
10 bytes
3.4E-4932 to 1.1E+4932
19 digits
8-bit AVR long double
Same as 8-bit AVR float
The float data type should be familiar to you by now as it was used in previous C
example programs. In C there are two other floating point data types, namely, double
and long double which allow even bigger floating point numbers to be stored. In the
GCC compiler that is used by Atmel Studio, neither double nor long double have been
implemented for 8-bit AVR microcontrollers. These two floating point data types can be
used in a C program, but they are implemented in exactly the same way as the 4 byte
float and don’t add any extra precision.
● 199
C Programming with Arduino
The float_check project prints out the size of a float in bytes, the decimal digit
precision, minimum positive value and maximum positive value of a float on an 8-bit AVR.
Project name: float_check
Project location: CH10\float_check
main.c
#include <stdio.h>
#include "stdio_setup.h"
#include <float.h>
int main(void)
{
float
num_float
= 0.0;
double
num_double
= 0.0;
long double num_lng_double = 0.0;
UartInit();
printf("float is %d bytes, precision %d, min %e, max %e\r\n",
sizeof(num_float), FLT_DIG, FLT_MIN, FLT_MAX);
printf("double is %d bytes\r\n", sizeof(num_double));
printf("long double is %d bytes\r\n", sizeof(num_lng_double));
}
while(1);
Program Output
float is 4 bytes, precision 6, min 1.175494e-38, max 3.402823e+38
double is 4 bytes
long double is 4 bytes
Only the number of bytes that the double and long double data types consist of are
printed out, and it can be seen that they are not implemented to their proper specification
shown in Table 10-3 on page 199.
Sizes for FLT_DIG, FLT_MIN and FLT_MAX are defined in the header file float.h which is
included at the top of the C source file. These definitions contain the compiler specific
sizes implemented for the floating point data type. C compilers implement the floating
point data type in what is known as the IEEE 754 technical standard.
Floating point numbers in C are usually best avoided on microcontrollers that have small
amounts of memory, as the libraries that add support for floating point tend to use up
a fair amount of memory. This is especially true for very small AVR microcontrollers
that may have only 2k bytes of flash and a few hundred bytes of RAM. Higher precision
floating point numbers were probably not implemented in the 8-bit AVR GCC compiler for
this reason and will probably never be needed.
10.13 • Casts
A cast forces an expression to become the type represented by type name of the cast and
has the format: (type name) expression
The following example casts an int type to a char type and a float type to an int type.
● 200
Chapter 10 • Previous C Topics Revisited
int an_int = 4567;
char ch;
float flt = 1.234;
ch = (char)an_int;
an_int = (int)flt;
// cast an int to a char
// cast a float to an int
The first cast tells the compiler that you want to convert an int to a char. Because a cast
is used, the compiler will not complain by issuing a warning message. The compiler would
normally complain because a char is only one byte long and an int is two bytes long.
The resulting operation will put the least significant byte (lowest 8 bits) of the int into
the char, the upper 8 bits will be discarded.
The second cast forces a floating point number to become an integer. This will result in
only the integer part of the floating point number being assigned to the integer variable
and the fractional part after the decimal point being discarded.
10.14 • Exercises
10.14.1 • Count Down for Loop
Use a for loop to count down from 10 to 0 and send each count value to be displayed in
the Tera Term terminal window. When counting down, use the initialisation expression
to initialise the loop variable to 10, the control expression to break out of the loop when
the loop variable is less than 0 and the adjustment expression to decrement instead of
increment the loop variable.
10.14.2 • LED Control using a switch Construct
Use a switch statement that has two cases to switch a LED on or off by a user sending
characters from a keyboard. When ‘a’ is sent, switch the LED on, when ‘b’ is sent, switch
the LED off.
10.15 • Solutions
10.15.1 • Solution to 10.14.1
Count down project created using template file.
Project name: CH10_1_count_down
Project location: Exercises\CH10_1_count_down
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
int count;
UartInit();
for (count = 10; count >= 0; count--) {
● 201
C Programming with Arduino
}
printf("count = %d\r\n", count);
}
while (1);
10.15.2 • Solution to 10.14.2
LED select project created using template file.
Project name: CH10_2_LED_select
Project location: Exercises\CH10_2_LED_select
main.c
#include <avr/io.h>
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
unsigned char rx_byte;
DDRC = 0x01;
// LED on PC0
UartInit();
while(1) {
if (UCSR0A & 0x80) { // data received?
rx_byte = UDR0;
// get the byte
switch (rx_byte) {
case 'a':
PORTC = 0x01;
// switch LED on
break;
}
}
}
}
case 'b':
PORTC = 0x00;
// switch LED off
break;
10.16 • Summary
● 202
•
The break and continue statements are used to change the flow of a program
and are commonly used in loops.
•
The do while loop will always run the body of the loop once and then check the
loop expression.
•
The for loop is most often used to count or perform some action a set number
of times.
•
Loops and decisions can be nested in C. Using proper indenting in source code
makes nesting easier to read and understand so be sure to always indent a
program clearly.
Chapter 10 • Previous C Topics Revisited
•
The switch statement is another decision making construct in C which can be
easier to read than using the if – else if – else construct especially when
there are many comparisons being made.
•
The conditional operator is another decision making operator in C.
•
More than one value can be returned from a function by passing the addresses
of variables to a function so that the function can store the values to be
returned in these variables.
•
All variables in a C program have "scope". The scope of a variable determines
which functions in the program can "see" and use the variable. Variables
defined within a function have function scope as they can only be used by the
function in which they are defined. Global variables have file scope and can be
seen by all of the functions within the file that they are defined.
•
Static variables are not "destroyed" (freed up) when a function returns. A static
variable and its contents are preserved between function calls to the function
that contains the static variable.
•
Casts allow a programmer to force one type of variable’s value to be stored in
another type of variable.
● 203
C Programming with Arduino
● 204
Chapter 11 • Arrays and Strings
Chapter 11 • Arrays and Strings
What you will do in this chapter:
•
See how arrays are used to access sequential memory locations
•
See how strings are used and stored in memory
•
Use C standard library string functions
•
Write functions that take strings and arrays as arguments
You have already worked with strings in C when using the printf() function. C does
not have a string data type but instead uses arrays of characters. An array of characters
is simply a number of sequential characters in memory. In C a string is an array of
characters, but it is also possible to have an array of a different data type. We will start by
looking at arrays in general and see how to define and use arrays in C. We will then take
a closer look at strings and some standard library functions used to manipulate them.
11.1 • Arrays
An array is a set of consecutive memory locations that store data of a specified type. The
key principal here is consecutive, as this is what makes arrays possible. As an example, in
C an array of 5 integers is defined as follows:
int arr_num[5];
This tells the compiler to make space for an array that consists of five integer numbers.
Each element (integer) in the array can be accessed using array notation in C. If we
wanted to assign a value to each element in the array, it can be done as follows:
arr_num[0]
arr_num[1]
arr_num[2]
arr_num[3]
arr_num[4]
=
=
=
=
=
20;
34;
9;
1023;
700;
There are five elements in this array, but they are indexed starting at 0, so the five
elements are accessed with indexes from 0 to 4 and not 1 to 5.
The array definition:
int arr_num[5];
is similar to defining 5 separate integer variables:
int arr_num0, arr_num1, arr_num2, arr_num3, arr_num4;
Defining separate variables, however does not guarantee that they are consecutively
placed in memory. The separate variables can also not be referenced with array notation
which, as we will see, is very useful.
The index_array program initialises an array of integers and then use a for loop to move
through the list of integers and send them out of the serial port.
● 205
C Programming with Arduino
Project name: index_array
Project location: CH11\index_array
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
int num_list[5] = {34, 789, 327, 1000, 45};
int index;
UartInit();
}
for (index = 0; index < 5; index++) {
printf("Element %d = %d\r\n", index, num_list[index]);
}
while (1);
Program Output
Element
Element
Element
Element
Element
0
1
2
3
4
=
=
=
=
=
34
789
327
1000
45
The first line inside main() defines an array of 5 integers and initialises each of these
integers to a value that appears between braces after the = sign. The initialisation has
the same effect as assigning a value to each element of the array as follows:
int num_list[5];
num_list[0] = 34;
num_list[1] = 789;
num_list[2] = 327;
num_list[3] = 1000;
num_list[4] = 45;
A for loop is used to count from 0 to 4. Inside this loop, the current count value (the
variable index) and each element of the array is printed out. By referencing each element
in the array with the current count that is counting from 0 through 4, we access each
element in the array from 0 to 4.
When index contains the value 0, then num_list[index] takes on the value of the
first element in the array i.e. 34 in this example. After index has been incremented
to 1, num_list[index], which is equivalent to num_list[1], now has the value of the
second element in the array i.e. 789. Remember that we are counting from 0, so the first
element in the array is indexed at position 0, the second at position 1 the third at position
2, etc. Figure 11-1 on page 207 shows the array used in this example in diagrammatic
format as it would appear in memory.
● 206
Chapter 11 • Arrays and Strings
Figure 11-1 An Array of Integers in Memory
The next program, called LED_seq (LED sequence), initialises an array with a series
of numbers that are written to LEDs to make them move through a sequence of
different combinations of off and on LEDs. An array used in this way is known as a
lookup table. Connect four LEDs on port pins PC0 to PC3 in the same way shown in
Chapter 8, Figure 8-10 on page 154 for the Uno and figure 8.11 for the MEGA – just
connect the bottom 4 LEDs. The values in the array are the values that are written to
the microcontroller port to switch the LEDs on and off. Create this project from scratch
without using the template file in Atmel Studio.
Project name: LED_seq
Project location: CH11\LED_seq
main.c
#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
int main(void)
{
unsigned char seq[] = {0x01, 0x02, 0x04, 0x08, 0x06,
0x09, 0x0F, 0x05, 0x0A, 0x05, 0x0A};
int ind;
DDRC = 0x0F;
}
// lower 4 bits of port C are outputs
while(1) {
for (ind = 0; ind < sizeof(seq); ind++) {
PORTC = seq[ind];
_delay_ms(1000);
}
}
Program Output
See Figure 11-2
An array of type unsigned char is defined at the beginning of main() that contains the
on and off number sequences to display on the LEDs. Figure 11-2 on page 208 shows
what the numbers in the array produce when written to the LEDs.
Each element of the array of unsigned characters is initialised to a value that will be
written to port C of the AVR. The notation for defining the array is as follows, with the
initialisation of each array element after the array definition:
unsigned char seq[]
● 207
C Programming with Arduino
The number of elements in this array is missing, and would normally be placed between
the square brackets as the previous example showed. Because we have initialised the
array with 11 values, the compiler will count the values and make the array the correct
size of 11. If we did not initialise the array, we would have to specify the size of the array,
as the compiler would then not know how big we intended to make the array.
After defining and initialising the array and defining the ind variable to be used as
an index into the array, Port C is set up to drive four LEDs on pins PC0 to PC3 before
entering the while(1) loop.
Figure 11-2 The Sequence of LED Patterns in the LED_seq Program
Inside the while(1) loop is a for loop that is used to access each element of the array
in sequence. When the for loop has counted to the last element of the array, it will start
at the beginning of the array again because the for loop is inside the continuous while
loop. When the top of the while(1) loop is reached, the for loop is initialised again and
counts through the 11 elements of the array again. In this way it counts from 0 to 10
continually.
The number to count up to in the for loop is obtained by using the C sizeof keyword.
This keyword returns the size of the array in bytes and will return the number of elements
in the array because each element is one byte wide. The reason for calculating the
maximum value to count to in the for loop this way is to make it easier to change the
patterns that are written out to the LEDs. To change the number of patterns, simply add
or remove values where the array is initialised. The size of the array is automatically
calculated by the compiler and the maximum number that the for loop must count to is
also adjusted automatically.
Inside the for loop, each number in the array (LED pattern) is written to port C. A delay
function is then called to leave the pattern displayed on the LEDs long enough to see.
After the delay, the next pattern is written to the LEDs to display.
The previous two C code listings showed how to create an array of integers and an array
of unsigned characters. Arrays of other data types can be created just as easily.
An array of floating point numbers:
float sample[30];
An array of long integers:
long lng_array[10];
We will now look at strings which are just arrays of characters.
● 208
Chapter 11 • Arrays and Strings
11.2 • Strings
Strings are useful in embedded systems wherever status information is to be displayed to
a user or received from a keypad or keyboard input. Strings can be displayed on an LCD
display or on a terminal emulator as we have been doing or even saved to SD card or disk
if the embedded system has one.
Strings are sequences or arrays of characters that are NULL terminated. This means
that the last character in a string must have a value of 0. NULL is the first number in the
ASCII table and has the escape sequence \0 and numerical value of zero.
The strings program demonstrates the use of string constants and string variables.
Create the project using the template file and connect to the terminal program to see the
output.
Project name: strings
Project location: CH11\strings
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
char msg[4];
UartInit();
printf("%s\r\n", "Hello");
msg[0] = 'B';
msg[1] = 'y';
msg[2] = 'e';
msg[3] = 0;
printf("%s\r\n", msg);
}
while(1);
Program Output
Hello
Bye
At the beginning of this program a character array, msg, is defined and allocated 4 bytes
of memory. The string "Hello" used in the printf() statement is an example of a string
constant. This string cannot be changed after compiling when the program is running and
is therefore constant.
The msg array is used to demonstrate how a string is built up of characters and
terminated with 0. It is known as a string variable as its contents can be modified when
the program is running. As can be seen, the word "Bye" is written to the msg array by
writing one character at a time to each element of the array. A zero is written to the last
element in the array in order to correctly terminate the string which is now stored in this
array. The escape sequence \0 could also be used to terminate the sting and would also
result in a zero being written to the last element in the array, it would need to be written
between single quotes i.e. '\0'. The size of an array used to store a string must always
be one element longer than the string to leave space for the NULL terminating character
● 209
C Programming with Arduino
of the string.
We could have made the msg array in this program longer than 4 elements; the other
elements of the array would then not be used. If the array was made too short and we
tried to write a character to the array that was beyond the end of the array, memory that
may have been allocated to other variables could then be overwritten causing unexpected
program behaviour.
Figure 11-3 shows what the string constant and string variable from the strings program
look like in memory. The addresses shown would be whatever addresses were assigned
to the strings during the build process. It is not important what these addresses are; only
that they fall in the correct range of the microcontroller’s memory map and that each
element in the string is consecutively placed in memory (consecutive addresses).
Figure 11-3 The String Constant (left) and String Variable (right) used in the
strings Program
11.2.1 • Writing to a String
The write_string program allows a user to send a string to the embedded system from
the terminal emulator program over the serial / USB port. Create this project using the
template file in Atmel Studio.
Project name: write_string
Project location: CH11\write_string
main.c
#include <avr/io.h>
#include <stdio.h>
#include "stdio_setup.h"
#define MAX_STR_LEN 35
int main(void)
{
char in_str[MAX_STR_LEN];
int index = 0;
UartInit();
printf("\r\nEnter a string, maximum length %d\r\n", MAX_STR_LEN - 1);
while (index < (MAX_STR_LEN - 1)) {
if (UCSR0A & 0x80) {
/* character received? */
in_str[index] = UDR0;
/* put char into array */
if (in_str[index] == '\r') { /* finished when Enter pressed */
● 210
Chapter 11 • Arrays and Strings
break;
}
printf("%c", in_str[index]); /* echo character on serial port */
index++;
}
}
}
in_str[index] = 0;
/* NULL terminate the string */
printf("\r\nYou entered the string: %s\r\n", in_str);
while (1);
Example Program Output 1, Enter Key Pressed
Enter a string, maximum length 34
Hello, Arduino AVR!!!
You entered the string: Hello, Arduino AVR!!!
Example Program Output 2, Maximum String Length Reached
Enter a string, maximum length 34
QWERTYUIOPASDFGHJKLZXCVBNM12345678
You entered the string: QWERTYUIOPASDFGHJKLZXCVBNM12345678
This program prints a message in Tera Term asking the user to enter a string with a
maximum length of 34. As the user enters each character of the string in the terminal
window, each character is sent back to the terminal (echoed) so that the user can see
what he is typing.
When the user presses the Enter key, the program null terminates the string and then
displays the string in the terminal window. If the user types more characters than can
fit in the in_str string variable array, the string is also null terminated and displayed
before the user can press the Enter key. This prevents more characters being written to
the string array than can fit into the array, which would result in memory below the array
being written to, possibly corrupting other program data.
Let’s have a look at how the write_string program works. Firstly a maximum size for the
string that the user will enter is defined using the #define directive. If you would like to
change the maximum size of string that the user can enter in this program, you just need
to change it in one place, where it is defined, and the array size and bounds checking
throughout the program will automatically be adjusted.
A message is sent to the terminal from the Arduino, prompting the user to enter a string.
The maximum length of the string that can be entered by the user is also sent as part of
the text message. This value is calculated by subtracting 1 from the length of the array
that is used to store the string that will be input by the user. The reason for subtracting
1 from this length is because the null terminating character needs to be stored in the last
element of the array if the maximum number of characters allowed in the string is used.
A while loop is used to get the characters from the user via the serial port. The while
loop is set to terminate if the user types more characters than can be stored in the array
set aside for these characters. This is done by checking the index variable that is used
to access each element of the array to see if it has gone out of bounds. We actually
have to check this value against the length of the input array minus 2. This is firstly to
accommodate the null termination character and secondly because the last element in
the array is one less than the length of the array (because the first element in the array is
counted as 0 and not as 1). The loop expression is as follows:
● 211
C Programming with Arduino
index < (MAX_STR_LEN - 1)
This checks that the index is less than the maximum string length minus one, which is
equivalent to checking that the index is less than or equal to the maximum string length
minus two. In other words we could have written:
index <= (MAX_STR_LEN - 2)
Inside the while loop, there is an if statement that checks to see if a character was
received on the serial port. The body of the if statement is only run if a character is
received.
When a character is received, it is saved to the character array in_str. The variable
index is used to determine which element of the array the character is saved to. This
variable is initialised to 0, so stores the first character to the first element (element 0)
of the array. The index variable is incremented after each character is saved so that the
next character will be saved to the next element in the array.
After a character has been saved to the array, the character is checked to see if it is the
carriage return character (\r) i.e. we are checking to see if the user pressed the Enter
key. If the Enter key was pressed, the break statement is used to exit out of the while
loop without executing a further statement in the while loop. If Enter was not pressed,
the character that the user typed is sent back to the terminal via the serial port by
calling the printf() function which transmits a single character. After echoing the typed
character back to the user, the index variable is incremented so that it will access the
next element in the in_str array. The while loop’s loop expression is checked to see if
index is out of bounds or not and then the if statement in the loop is run again to check
for the next character from the serial port.
The while loop is exited if the user enters too many characters or if the Enter key is
pressed. If either of these two conditions occur, the null terminator is added to the string
in the element that index is accessing. This would be the last element in the array if the
loop was exited because the array was full. In the case of Enter being pressed, the \r
character is saved to the array and then overwritten by the null terminating character as
index is not incremented after breaking out of the loop. A text message is then displayed
in the terminal with the string that the user entered, which is now saved in the in_str
array.
Finally program execution drops into the infinite while(1) loop. Press the reset button on
the Arduino to run the program again.
11.2.2 • Initialising a String
The init_string project, created from the template file, shows two ways of initialising a
string variable with data.
Project name: init_string
Project location: CH11\init_string
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
char str1[] = { 'F', 'i', 'r', 's', 't', '\r', '\n', '\0' };
char str2[] = "Second\r\n";
UartInit();
● 212
Chapter 11 • Arrays and Strings
printf("%s", str1);
printf("%s", str2);
}
while(1);
Program Output
First
Second
Because a string is an array, we can initialise a string in the same way that we initialise
an array of integers. The string str1 in this example is initialised with individual
characters. Note that the null terminating character must also be put into the string.
The second string, str2, in this program shows an easier way of initialising a string by
putting it between double quotes. When this method is used, the compiler automatically
inserts the null terminating character at the end of the string.
11.2.3 • C Library String Functions
The C standard library provides a number of functions that are used for manipulating
strings. Prototypes for these functions are found in the header file string.h. Table 11-1
shows some of these string functions and the str_func program demonstrates their use.
Table 11-1 Some of the String Functions Available in the C Standard Library
Function
Description
strcat(str1, str2)
Concatenates (appends) str2 to str1
strcmp(str1, str2)
Compares two strings. Returns 0 if the strings are equal, < 0
if str1 is less than str2, > 0 if str1 is greater than str2
strcpy(str1, str2)
Copies str2 into str1
strlen(str)
Returns the length of the string str, excluding the null- terminating
character
Project name: str_func
Project location: CH11\str_func
main.c
#include <stdio.h>
#include <string.h>
#include "stdio_setup.h"
int main(void)
{
char str1[10] = "abc";
char str2[] = "def";
UartInit();
printf("The length of str1 is %d\r\n", strlen(str1));
printf("Comparing str1 & str2: %d\r\n", strcmp(str1, str2));
● 213
C Programming with Arduino
printf("Comparing str2 & str1: %d\r\n", strcmp(str2, str1));
strcat(str1, str2);
printf("str1 is now: %s\r\n", str1);
strcpy(str1, str2);
printf("str1 is now: %s\r\n", str1);
printf("Comparing str1 & str2: %d\r\n", strcmp(str1, str2));
}
while(1);
Program Output
The length of str1 is 3
Comparing str1 & str2: -3
Comparing str2 & str1: 3
str1 is now: abcdef
str1 is now: def
Comparing str1 & str2: 0
String str1 is forced to make space for a string that is 9 characters long plus the nullterminating character by specifying the length of the string as 10 between the square
brackets when the string is defined. str1 is, however, initialised with a string that is only
3 characters long. The reason for allocating more space than necessary in this program is
so that there will be enough space to concatenate a string to this one without going out of
the array bounds.
The strlen() function is called first and returns the length of string str1. The length
returned is 3 which is the length of the string excluding the null-terminating character.
strcmp() is called twice to compare the two initialised strings. The comparison is done by
this function a character at a time. The difference between the first set of characters that
do not match is returned. In the example, "abc" (str1) is compared with "def" (str2).
The first characters that do not match are ‘a’ and ‘d’. Looking at the ASCII table, one can
see that these characters have values of 97 and 100. The difference between the two
values is 100 – 97 = 3. When these strings are compared again, they are compared in
reverse order, so the difference between the two is now 97 – 100 = -3.
The strcat() function is called to concatenate str2 onto str1. Some of the extra space
made available when defining str1 is now used to store the extra string added to the end
of the string contained in str1. str1 changes from "abc" to "abcdef" after concatenation.
strcpy() is called to copy str2 into str1. This action overwrites the contents of str1
and replaces it with the contents of str2.
strcmp() is called again to compare the two strings. Because str2 was copied to str1,
they are now equal and strcmp() returns a value of 0.
11.3 • Arrays and Addresses
11.3.1 • Arrays and Array Element Addresses
Unlike the name of a single variable that takes on the value of the variable, the name
of an array is the address of the array. For example if we have an array of characters as
follows:
char processor[] = "AVR";
● 214
Chapter 11 • Arrays and Strings
The address of the array and therefore the address of the first element in the array is
the name of the array, and in this example is processor. We do not get the address by
writing &processor as with single variables.
The value that the first element of the array is holding is obtained by using array
notation: processor[0]. The contents of each of the other elements in the array can be
accessed in a similar fashion e.g. processor[1], etc. The address of any element can be
obtained by using the address operator with array notation e.g. &processor[1] is the
address of the second element in this array. The array_addr project clarifies how array
addresses work.
Project name: array_addr
Project location: CH11\array_addr
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
char processor[] = "AVR";
int i;
UartInit();
printf("Address of array is %u\r\n", (unsigned int)processor);
for (i = 0; i < (sizeof(processor) - 1); i++) {
printf("addr = %u, data = %d (%c)\r\n",
(unsigned int)&processor[i], processor[i], processor[i]);
}
}
while(1);
Program Output on Arduino Uno
Address of array is 2296
addr = 2296, data = 65 (A)
addr = 2297, data = 86 (V)
addr = 2298, data = 82 (R)
This program takes the string "AVR" and prints out the address of the array (i.e. the
address of the first element of the array) by using the name of the array. It then prints
out the address of each element in the array as well as the contents of each element in
decimal and character format.
When the address of the array is printed using the name of the array, it is cast to an
unsigned integer ((unsigned int)processor) to tell the compiler that we want to
display the address of the array as an unsigned integer. The address of the array is a
pointer to a character (char *), so the cast stops the compiler issuing a warning as we
specify that we want to print this address as an unsigned integer. An unsigned integer
was chosen because an address will never be negative.
The for loop in this program prints out the address of each element of the array as well
as the contents of each element of the array as previously explained. The terminating
condition of the for loop (sizeof(processor) - 1) automatically calculates the element
number of the last element of the string array. We could have just used the value 3 here.
● 215
C Programming with Arduino
Note that the sizeof keyword returns the size of the string including the null-terminating
character which is 4 in this program and is why we need to subtract 1.
The null terminator in the processor[] array also has an address which will be the
address of the last character in the array plus 1 for an array of 8-bit bytes, but was not
printed in this program because the value of the null terminator, which is zero, is not a
printable character. In other words, the address of the last element in the array which
holds the null terminator could be printed out, but the value that the null terminator holds
can not be printed out as a character. It would print out as a number which would be 0,
but there is no corresponding character to print from the ASCII table when printed as a
character.
11.3.2 • Passing an Array to a Function
Because the name of an array is the address of the array, we can only pass the address
of an array to a function. It is actually more desirable to pass the address of an array to
a function than to copy the entire array to a local array of the function. When a function
is passed the address of an array, we are actually passing a pointer to the array to the
function. The function can be written to accept the pointer to the array using either array
notation or pointer notation.
These two functions can both take an array of type char as arguments:
void Func1(char str[]);
void Func2(char *str);
The pass_array program uses two functions to demonstrate how to pass an array to a
function as shown above.
This program defines and initialises two arrays – a character array that is initialised with
a string and an array of integers. PrintCharArray() is called and passed the address of
the character array called text[]. PrintIntArray() is called and is passed the address
of the integer array samples[] along with the length of the integer array.
Each of the functions called demonstrate a different way to access an array, but they are
essentially doing the same thing. PrintCharArray() accesses the address of the array
with a local character pointer that is defined with array notation – char str[]. The first
while loop in this function uses array notation to print the string out a character at a
time. The second while loop does exactly the same thing, but this time using pointer
notation. Both loops will only exit if the character that the pointer is pointing to is equal to
0 which is the null terminator.
Project name: pass_array
Project location: CH11\pass_array
main.c
#include <stdio.h>
#include "stdio_setup.h"
void PrintCharArray(char str[]);
void PrintIntArray(int *ptr, int length);
int main(void)
{
char text[] = "Start pump\r\n";
int samples[] = {115, 2000, 95, 9 };
UartInit();
● 216
Chapter 11 • Arrays and Strings
}
PrintCharArray(text);
PrintIntArray(samples, sizeof(samples) / 2);
while (1);
void PrintCharArray(char str[])
{
int i = 0;
}
while (str[i] != 0) {
printf("%c", str[i]);
i++;
}
while (*str != 0) {
printf("%c", *str);
str++;
}
void PrintIntArray(int *ptr, int length)
{
int i;
}
for (i = 0; i <
printf("%d:
}
for (i = 0; i <
printf("%d:
ptr++;
}
length; i++) {
%4d\r\n", i, ptr[i]);
length; i++) {
%4d\r\n", i, *ptr);
Program Output
Start pump
Start pump
0: 115
1: 2000
2:
95
3:
9
0: 115
1: 2000
2:
95
3:
9
To move from character to character through the array using pointer notation, the
pointer itself is incremented using the increment operator (str++) which increments the
address that the pointer is holding. If the array was at address 0x08A2, the pointer str
would contain this address after the PrintCharArray() function was called. After each
increment of this address in str, this pointer would be pointing to the next element in
the array at the next address. This function demonstrates that pointer notation and array
notation can be used interchangeably. You can use either one or mix them to achieve the
same results.
PrintIntArray() is passed the address of the array which it stores in the local integer
pointer ptr. In the first for loop in this function, array notation is used with this pointer
to access each element in the array that the pointer is pointing to. The second for
loop uses pointer notation to do exactly the same thing as with the PrintCharArray()
function. Again the pointer variable itself is incremented, which results in it pointing to
● 217
C Programming with Arduino
the next address and therefore the next element in the array. It was necessary to pass
the length of this array to this function as there is no other way to determine the number
of integers in the array, unlike strings in which the null-terminator can be checked for.
When PrintIntArray() is called, it is passed the size of the samples[] array in bytes
divided by two as the second argument, shown in the following line of code from the
above program.
PrintIntArray(samples, sizeof(samples) / 2);
The reason for dividing the number of bytes in the array by two is because each integer in
the array is two bytes wide on an 8-bit AVR using the GCC compiler. The four integers in
the samples[] array take up 4 × 2 bytes = 8 bytes, but we want to pass the number of
integers to the function which is 8 bytes ÷ 2 = 4 integers.
When the pointer variable ptr is incremented in the PrintIntArray() function, it takes
into account that the array consists of two byte wide integers and increments the address
in the pointer by two address locations, or two byte addresses at a time. If this were not
so, the pointer would first point to the first byte of the first integer, then the second byte
of the first integer, followed by the first byte of the second integer and the second byte
of the second integer, etc. In other words the pointer must move up an integer, or two
bytes, at a time to make sure that it points to the next integer and not the next byte. If
the data type of the array was long, the pointer would move up four bytes at a time to
point to the next four byte wide long integer.
11.4 • Strings as Pointers
The string_ptr project shows that a string can be initialised as a pointer, again showing
the similarities between pointer and array notation.
Project name: string_ptr
Project location: CH11\string_ptr
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
char *status1 = "Pump started.\r\n";
char status2[] = "Mixer started.\r\n";
UartInit();
printf("%s", status1);
printf("%s", status2);
}
while(1) {
}
Program Output
Pump started.
Mixer started.
● 218
Chapter 11 • Arrays and Strings
The first string in this program is initialised using pointer notation. In the program it
can now be accessed using pointer or array notation. The second string is initialised as
we have done before with array notation. It can also be accessed in the program with
array or pointer notation. Note that initialising a string as a pointer can only be used with
strings. If you wanted to have an array of integers or floating point numbers, you would
have to initialise the array with array notation, although you are free in the program to
access the array with either notation.
11.5 • Writing to a String using sprintf()
The sprintf() function (string print formatted) can be used in the same way as the
printf() function, except that instead of printing to the standard output of a system, it
prints to a string. This allows all of the printf() formatting functionality to be used on a
string. The string_print project demonstrates the use of the sprintf() function.
Project name: string_print
Project location: CH11\string_print
main.c
#include <avr/io.h>
#include <stdio.h>
#include "stdio_setup.h"
void UartTxMsg(char *msg);
int main(void)
{
char the_string[30];
unsigned int data = 64809;
UartInit();
sprintf(the_string, "Dec: %u, Hex: %X\r\n", data, data);
UartTxMsg(the_string);
}
while(1) {
}
void UartTxMsg(char *msg)
{
int ch_num = 0;
}
while (msg[ch_num] != 0) {
while(!(UCSR0A & 0x20));
UDR0 = msg[ch_num];
ch_num++;
}
// wait until ready to tx
// transmit byte
Program Output
Dec: 64809, Hex: FD29
In the program, sprintf() is used to print the value of the variable data to the
the_string[] array in both decimal and hexadecimal formats. The array that will contain
the formatted output string is passed to sprintf() as the first argument. The other
● 219
C Programming with Arduino
arguments to the sprintf() function are the same as for printf().
The string variable the_string[] was made extra long, i.e. 30 bytes, in order to prevent
sprintf() from writing to memory beyond this array. In C there is no bounds checking
of arrays which makes it possible for a program to write data beyond an array and modify
data that it should not. It is up to the programmer to do bounds checking or to make an
array sufficiently large enough so that a program will not be able to write to memory that
is beyond the end of the array.
In the string_print program, a function called UartTxMsg() is used to send the string that
was written to by sprintf() out of the serial port for display in the terminal program and
is shown below.
void UartTxMsg(char *msg)
{
int ch_num = 0;
}
while (msg[ch_num] != 0) {
while(!(UCSR0A & 0x20));
UDR0 = msg[ch_num];
ch_num++;
}
// wait until ready to tx
// transmit byte
The address of the string to transmit on the serial port is passed to UartTxMsg() and is
available in this function as the pointer called msg. Code in the first while loop sends one
character from the array or string that msg is pointing to out of the serial port at a time.
Array notation is used in the first while loop expression to check for the null-terminating
character which signals the end of the string that is being transmitted. Inside the first
while loop, a second while loop with no body is used to wait until the transmitter is ready
before sending a character from the array to UDR0 of the microcontroller’s USART which
will transmit the byte.
After a character from the array is transmitted, the index into the array stored in the
variable ch_num is incremented to access the next element in the array. In this way, each
character in the array that msg is pointing to will be transmitted by the serial port until
the null-terminating character is found which will cause program execution to exit the first
while loop and the function to return.
The function could be written in a more compact way by inserting the post-increment
operation on the ch_num variable into the array brackets of the preceding statement. In
this case, the function would look as follows.
void UartTxMsg(char *msg)
{
int ch_num = 0;
}
while (msg[ch_num] != 0) {
while(!(UCSR0A & 0x20));
UDR0 = msg[ch_num++];
}
// wait until ready to tx
// transmit byte
Because a post-increment operation is applied to ch_num, this variable will not be
incremented until after the statement that it is used in has been executed.
● 220
Chapter 11 • Arrays and Strings
11.6 • Multidimensional Arrays
Multidimensional arrays are arrays that contain other arrays and can be two or more
levels deep. A two dimensional array can be defined in C as follows:
int array[3][4];
This line tells the compiler to make space in memory for three arrays that are each four
integers long. Both the array and each element in the array can be accessed using array
notation. To assign a value to the second element of the first array, we would write:
array[0][1] = 10;
A two dimensional array can be initialised when it is defined as follows:
int array[3][4] = {{1, 2, 3, 4}, {11, 12, 13, 14}, {21, 22, 23, 24}};
Figure 11-4 shows what this array would look like in memory.
Figure 11-4 A Two Dimensional Array In Memory
The multi_seq program uses a two dimensional array to store three sets of patterns
that are written to four LEDs. A pattern is displayed on the LEDs continually and can be
changed to one of the other patterns by pressing a push-button switch connected to one
of the port pins.
A switch is connected to one of the Arduino pins as previously done in Figure 9-1 on page
163, except that the switch is connected to port pin PC5 which is Arduino pin A5 on the
Uno and Arduino digital pin 32 on the MEGA. Four LEDs are connected to port pins PC0 to
PC3 using series resistors as before. The circuit shown in Appendix C on page 335 can
be built and will be used with many of the programs that follow. Create the project as a
new GCC C project and not from the template file.
Project name: multi_seq
Project location: CH11\multi_seq
main.c
#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
void GetSwitch(void);
int prog_num = 0;
int main(void)
{
unsigned char seq[3][4] =
{{0x09, 0x06, 0x0F, 0x06},
{0x08, 0x04, 0x02, 0x01},
{0x08, 0x09, 0x0D, 0x0F}};
int ptrn;
DDRC = 0x0F;
// PC0 to PC3 are outputs for LEDs
while (1) {
● 221
C Programming with Arduino
}
}
for (ptrn = 0; ptrn < 4; ptrn++) { // move through each set of arrays
PORTC = seq[prog_num][ptrn];
// write the pattern from the chosen array
GetSwitch();
// read switch and delay
}
void GetSwitch(void)
{
int del_cnt = 100;
}
while (del_cnt--) {
if (PINC & 0x20) {
_delay_ms(40);
while (PINC & 0x20);
prog_num ++;
if (prog_num >= 3) {
prog_num = 0;
}
}
_delay_ms(10);
}
//
//
//
//
//
read switch value on PC5
debounce switch
wait for switch release
move to next LED sequence
stay within array bounds
// delay * del_cnt = LED on time
Program Output
LEDs flash the pattern from each sub-array. Pressing the switch changes between sub-arrays.
Inside main() the program defines and initialises a two-dimensional array called
seq[][] that contains three sets of patterns that are to be written to the LEDs. DDRC is
used to set up port C pins PC0 to PC3 as outputs that connect to the LEDs and the upper
four bits as inputs including PC5 which is connected to a switch.
The while(1) loop contains a for loop that continually writes a pattern to the LEDs
from the two dimensional array. You have seen this before with a one dimensional array,
except that this time the set of patterns that is output from the two dimensional array is
selected by pressing a push-button.
Global variable prog_num is incremented by GetSwitch() each time that the push button
switch is pressed. When prog_num is incremented to 3, it is reset back to a value of 0
which selects the first sub-array in the two-dimensional array ensuring that this variable
selects one of the three sub-arrays which will be either 0, 1 or 2.
Inside the for loop, the following line of code writes the bit pattern to the LEDs.
PORTC = seq[prog_num][ptrn];
In this line of code, prog_num stays the same until the push-button switch is pressed and
has an initial value of 0 which selects the first sub-array. As the for loop increments the
value of ptrn each time through the loop, each pattern from the first sub-array is written
to the port C LEDs in turn. When the push-button is pressed for the first time, prog_num
is incremented and the for loop now sends each bit pattern from the second sub-array
to port C. The second time that the push-button switch is pressed, bit patterns from
the third sub-array are written to the port C LEDs. The next time that the push-button
is pressed, prog_num is reset to 0 and the first sub-array bit patterns are sent to port C
again.
● 222
Chapter 11 • Arrays and Strings
The GetSwitch() function serves three purposes – it provides a delay between each bit
pattern written to the LEDs, it checks whether the push-button switch has been pressed
and it provides switch debouncing.
The while loop in GetSwitch() together with _delay_ms() create the delay time for the
LED pattern. The delay function is set to delay for 10ms times 100 passes through the
loop which creates a 1000ms or 1s delay. The delay was created this way so that the
state of the push-button switch can be checked every 10ms, otherwise the program may
not see that the switch was pressed if the delay between checking the switch state is too
long.
The following line of code is used to check if the push button switch is closed.
if (PINC & 0x20) {
This code uses a mask number to check if bit 5 of the PINC register is set which
corresponds to port pin PC5. If the switch is closed, bit 5 will be set and the code in the
body of the if statement is run. The next two lines of code, shown below, are used to
debounce the switch and cause the program to wait until the switch is released or opened
before the rest of the program is run.
_delay_ms(40);
while (PINC & 0x20);
Switch bounce is caused when a switch is closed and the switch contacts do not close
and stay closed, but actually "bounce" for a very short period of time. By bounce, we
mean that the switch contacts close and open a few times until they finally settle in
the closed position. Switch contact bouncing is due to the mechanical nature of the
switch and occurs with all mechanical contact switches. It is necessary to debounce the
switch in software by providing a small delay after the switch is closed that allows the
switch contacts to settle – this is the 40ms delay in the above code. If the switch is not
debounced, it appears to the microcontroller that the user has closed and opened the
switch a few times, which will cause the LED pattern displayed to skip over one or two of
the patterns in this program. The while loop shown in the above code waits until the user
releases the switch, which will cause the loop expression to evaluate to false, before the
next line of code is run. Holding the switch closed will cause the currently displayed LED
pattern to freeze on the LEDs until the switch is released.
After the switch is released, the global variable called prog_num that is used to access
sub-arrays is incremented and kept in range so that it is not able to try and access a subarray that does not exist.
The pattern selected by each switch press is done in the array as shown in Figure 11-5
on page 224. The global variable prog_num can be seen by every function in the source
file so does not have to be passed to the function as an argument or returned by the
function.
● 223
C Programming with Arduino
Figure 11-5 Accessing Elements in a Two-Dimensional Array
The next example program, status_array, shows how an array of strings is initialised and
used for displaying status messages of an imaginary embedded controller system. Create
this project using the template file.
Project name: status_array
Project location: CH11\status_array
main.c
#include <stdio.h>
#include "stdio_setup.h"
#define NUM_MSGS 5
int main(void)
{
int msg_num = 0;
char status[NUM_MSGS][25] = {"Belt Jam Alarm.",
"Conveyor Running.",
"Conveyor Stopped.",
"Out of Paper.",
"System Ready."};
}
UartInit();
for (msg_num = 0; msg_num < NUM_MSGS; msg_num++) {
printf("%s\r\n", status[msg_num]);
}
while (1);
Program Output
Belt Jam Alarm.
Conveyor Running.
Conveyor Stopped.
Out of Paper.
System Ready.
The two dimensional array status[][] is initialised with strings which are of course
themselves arrays. The size of each string can be 25 characters long including the nullterminating character. For strings that are smaller than this, the extra bytes are wasted.
The program uses a for loop to send each string out of the serial port to be displayed
in the terminal window. The number that the for loop is counting through is used as an
● 224
Chapter 11 • Arrays and Strings
index into the two dimensional array to print each string using printf().
A more efficient method can be used to initialise an array of strings and prevent memory
being wasted by having to specify a fixed length for each string. Initialising an array of
pointers to strings will not waste any bytes. A modified version of the previous program,
called status_point uses an array of pointers to strings.
Project name: status_point
Project location: CH11\status_point
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
int msg_num = 0;
char *status[] = {"Belt Jam Alarm.",
"Conveyor Running.",
"Conveyor Stopped.",
"Out of Paper.",
"System Ready."};
}
UartInit();
printf("Number of strings: %u\r\n\r\n", sizeof(status) / 2);
for (msg_num = 0; msg_num < sizeof(status) / 2; msg_num++) {
printf("%s\r\n", status[msg_num]);
}
while (1);
Program Output
Number of strings: 5
Belt Jam Alarm.
Conveyor Running.
Conveyor Stopped.
Out of Paper.
System Ready.
It is not necessary to define the size of the top-level array, or number of string in the
array, as this can be done by the compiler which allows us to add or remove strings from
the array without having to change the array size, as this is done automatically.
The array definition from the status_array project is changed from the following:
char status[NUM_MSGS][25]
To the following in the status_point project:
char *status[]
The changed line simply states that we want an array called status[] and that data
stored in the array is going to be of the type pointers to characters (char *). The
pointers in the array are 16-bit pointers and they point to character arrays or strings.
● 225
C Programming with Arduino
The number of strings that the array is pointing to can be calculated by finding the
number of bytes in the status[] array which is the number of bytes that all pointers in
the array consist of, and diving this number by two to get the number of two byte or
16‑bit pointers in the array. The number of pointers in the array is the same as the
number of strings that they are pointing to, because the array holds one pointer for each
string. In the code, the sizeof operator is used to calculate the number of strings
as follows:
sizeof(status) / 2
Both the first printf() statement and the for loop use the above code to calculate the
number of strings that the array points to.
Figure 11-6 shows a screen capture from the debugger in Atmel Studio of the status[]
array in memory on the Arduino Uno that better illustrates how the array of pointers to
strings works.
Figure 11-6 The status[] Array in Memory
In the figure, it can be seen that the status[] array contains five pointers, or five
addresses 0x012D, 0x013D, 0x014F, 0x0161 and 0x016F, but the actual array that holds
these five pointers is at address 0x08F2. At the right of the figure is the memory that
holds the actual strings that the array is pointing to. The memory box at the right displays
the address of the memory on the left, the hexadecimal byte of data at the address and
the ASCII character for the byte on the right. Where the byte is a non-printable ASCII
character, a dot is inserted.
The address of the first string in the array is found at the beginning of the array at
address 0x08F2 (or status[0]) and is 0x012D. If we look at the memory at address
0x012D we see the first string "Belt Jam Alarm". The next string address is stored in the
second array element, status[1], and is 0x013D. Looking in memory at 0x013D the
second string "Conveyor Running." can be seen. And so on for each of the strings that the
array elements point to.
11.7 • Exercises
11.7.1 • Button Message
Write a program that sends a message to the terminal program that says which button
on port pins PC4 and PC5 of the Arduino was pressed. The messages must be stored as
an array of pointers to strings. Wire two switches to the Arduino as shown in the circuit in
Appendix C on page 335.
● 226
Chapter 11 • Arrays and Strings
11.7.2 • Button Wait
Modify the program from the previous exercise to print the message to the terminal
window when the button is initially pressed so that the message is only displayed once.
Wait until the button is released before checking the button status again. Include switch
debouncing in the program.
11.8 • Solutions
11.8.1 • Solution to 11.7.1
Project created using the template file.
Project name: CH11_1_btn_message
Project location: Exercises\CH11_1_btn_message
main.c
#include <avr/io.h>
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
char *sw_msgs[] = {"BUTTON 1", "BUTTON 2"};
UartInit();
}
while(1) {
if (PINC & 0x10) {
// switch on PC4
printf("%s\r\n", sw_msgs[0]);
}
else if (PINC & 0x20) {
// switch on PC5
printf("%s\r\n", sw_msgs[1]);
}
}
11.8.2 • Solution to 11.7.2
Project created using the template file.
Project name: CH11_2_btn_wait
Project location: Exercises\CH11_2_btn_wait
main.c
#define
#include
#include
#include
#include
F_CPU 16000000UL
<avr/io.h>
<util/delay.h>
<stdio.h>
"stdio_setup.h"
void ButtonWait(unsigned char mask);
int main(void)
{
● 227
C Programming with Arduino
char *sw_msgs[] = {"BUTTON 1", "BUTTON 2"};
UartInit();
}
while(1) {
if (PINC & 0x10) {
// switch on PC4
printf("%s\r\n", sw_msgs[0]);
ButtonWait(0x10);
}
else if (PINC & 0x20) {
// switch on PC5
printf("%s\r\n", sw_msgs[1]);
ButtonWait(0x20);
}
}
void ButtonWait(unsigned
{
_delay_ms(30);
while(PINC & mask);
_delay_ms(30);
}
char mask)
// debounce for closing switch
// wait for switch release
// debounce for opening switch
As with most C programs there are many ways to write a program and your solutions to
these exercises may differ from the solutions in this book. The important things are that
the program must correctly solve the problem and must be easy to read and understand.
Always aim to write efficient code that uses as little memory and processing time as
possible.
11.9 • Summary
● 228
•
Arrays are sets of sequential data types.
•
Each element in an array can be accessed using array notation.
•
Strings are arrays of characters that are null terminated.
•
The address of an array in C is the name of the array.
•
Arrays can be accessed using array or pointer notation.
Chapter 12 • Bit Manipulation and Logical Operators
Chapter 12 • Bit Manipulation and Logical Operators
What you will do in this chapter:
•
See how to change individual bits inside a variable by using bitwise operators
•
Move the contents of a variable left or right using the shift operators
•
Use logical operators to combine expressions
12.1 • Bit Manipulation with Bitwise Operators
Bitwise operators enable a programmer to modify individual bits in a memory location
and can be used to operate on bytes or larger groups of bits. The C programming
language has four Boolean bitwise operators and two shift operators as shown in
Table 12-1. The bitwise AND operator (&) should be familiar to you.
Table 12-1 Boolean Bitwise and Shift Operators
Boolean Bitwise Operators
Operator
Operation
Example
Result
&
AND
1101 & 0111
0101
|
OR
1101 | 1001
1101
^
XOR (Exclusive OR)
1101 ^ 1001
0100
~
NOT (Compliment)
~1101
0010
Shift Operators
Operator
Operation
Example
Result
<<
Left Shift
1010 << 1
10100
>>
Right Shift
1010 >> 1
0101
Windows calculator will again come in handy when learning about bitwise operators as it
has all of the bitwise operators available. Figure 12-1 on page 230 shows the Windows
calculator and the equivalent C bitwise operators below it.
The bitwise operators can be tested with the calculator in binary or hexadecimal mode
and it is possible to change between these modes at any time while performing an
operation. The keystrokes used to calculate the examples in Table 12-1 on the calculator
are shown below from top to bottom – first put the calculator into Programmer mode
and then click the Bin radio button. An explanation of each bitwise operator follows.
1101 And 111 =
Note that the 0 place holder to the left of the second number is left out when entering the
second number and is not displayed on the calculator in the result i.e. the result displayed
is 101 and not 0101.
1101 Or 1001 =
1101 Xor 1001 =
1101 Not
The result of this operation on the calculator turns all of the invisible 0 place holders to
● 229
C Programming with Arduino
the left of the number into 1s and that is why the number is filled with 1s to its left. Click
the Byte radio button to shorten the number to 8 bits which is easier to read.
1010 Lsh 1 =
(Left shift by 1 bit)
1010 Rsh 1 =
(Right shift by 1 bit)
Figure 12-1 Bitwise Operations on the Windows Calculator
In Windows Calculator, the buttons have the following meaning with the equivalent
operators used in C shown in the middle:
Or
|
bitwise OR – the symbol used in C is the vertical pipe found on the \ key
Xor
^
bitwise XOR (exclusive OR)
Lsh
<<
left shift
Rsh
>>
right shift
Not
~
compliment
And
&
bitwise AND
Although RoL (rotate left) and RoR (rotate right) operate on bits in Windows Calculator,
they do not have an equivalent in the C language.
In order to understand what the Boolean bitwise operators do, we need to look at the
truth tables of these operators. The truth tables are shown in Figure 12-2. The truth
tables show the output of each bitwise operator when it is performed on the input bit(s).
Figure 12-2 Truth Tables for the Boolean Bitwise Operators
● 230
Chapter 12 • Bit Manipulation and Logical Operators
The NOT operator simply inverts each bit in a byte i.e. it changes all of the 0s to 1s and
1s to 0s.
In C, the other three operators are performed on two or more values of length 8 bits
(char), 16 bits (short or int) or 32 bits (long). Let’s take the OR operator as an example
and use it to operate on two bytes. In C the OR operator can be used as follows:
unsigned char byte1 = 0xC1;
unsigned char byte2 = 0x13;
unsigned char result;
result = byte1 | byte2;
The above example ORs two variables together and saves the result in a variable called
result. The example could also use the two numbers directly i.e. it could be used to OR
two constants together and store the result in a variable. Figure 12-3 shows how the OR
operation is done.
Figure 12-3 The Bitwise OR Operation on Two Bytes
Bit 0: 1 and 1 are inputs in the truth table, 1 is the output in the truth table
Bit 1: 0 and 1 are inputs in the truth table, 1 is the output in the truth table
Bit 2: 0 and 0 are inputs in the truth table, 0 is the output in the truth table
etc…. for each bit
Each bit of byte1 is ORed with the corresponding bit in byte2 from top to bottom
as shown in Figure 12-3. Using the OR truth table from Figure 12-2 on page 230,
bit 0 from each byte are ORed together i.e. 1 OR 1 = 1. The result is stored in the
corresponding bit of result i.e. bit 0 of result. Bit 1 is done in the same way: 0 OR 1
= 1. Bit 2: 0 OR 0 = 0. And so on for each bit from 0 to 7. The same operation would
be done for the AND and XOR if they were used instead of the OR operator, the only
difference would be the truth table used and the result produced.
12.1.1 • Why Do We Need Bitwise Operators?
Bitwise operators make hardware programming easy to do, as there are many cases
where a single bit needs to be changed in order to use a hardware device attached to a
microcontroller. Bitwise operators also allow a single bit to be tested as seen in some of
the previous code examples that use the AND operator to test the state of a single bit in
a byte that was read back from a hardware register. Before looking at the left and right
shift operators, we will look at some example programs that use the Boolean bitwise
operators.
When switching LEDs on and off using the I/O control registers of the AVR microcontroller
in previous example programs, we wrote data to all of the bits in each of the registers.
Writing to all of the bits in the registers is fine if we want to use all of the bits, but this
is not always the case. In cases where only some of the bits in a register need to be
● 231
C Programming with Arduino
changed, and the others left in their default state, we can use bitwise operators.
12.1.2 • Using the Bitwise AND and OR Operators
The bitwise_port project shows how to set up four pins on port C as outputs and write to
LEDs attached to them without changing the values in the other bits of the registers. For
this project, attach four LEDs with series resistors to port C pins PC0 to PC3 as previously
done using the circuit from appendix C. The upper four pins of this port will not be used
and will be left in their default states by using bitwise operators to leave the upper bits in
their corresponding registers in their default states. Create this project from scratch (GCC
C executable project) and not from the template file.
Project name: bitwise_port
Project location: CH12\bitwise_port
main.c
#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
int main(void)
{
DDRC = DDRC | 0x0F;
PORTC = PORTC | 0x04;
}
while(1) {
PORTC = PORTC & 0xFC;
_delay_ms(1000);
PORTC = PORTC | 0x02;
_delay_ms(1000);
PORTC = PORTC | 0x01;
_delay_ms(1000);
}
Program Output
LED on PC3 stays off, LED on PC2 stays on throughout the program.
LEDs on PC1 and PC0 flash continually in the following pattern with a delay in between:
Both LEDs off
PC1 on, PC0 off
PC1 and PC0 on
At the beginning of main() the bitwise OR operator is used to set the lower bits of DDRC
in order to set the corresponding pins PC0 to PC3 as output pins in the following line of
code.
DDRC = DDRC | 0x0F;
Using the bitwise OR operator to OR the contents of DDRC with 0x0F (00001111B) and
then saving the result back in DDRC leaves the upper bits of the register, bits 4 to 7,
unchanged, but sets the lower bits, bits 0 to 3. From the OR truth table we can see that
anything ORed with 1 is 1. All of the lower bits of the number 0x0F are 1, so the result
of the OR operation will be all 1s that will be placed in the lower nibble of DDRC. In the
● 232
Chapter 12 • Bit Manipulation and Logical Operators
OR truth table we can see that anything ORed with 0 will be itself (0 ORed with 0 is 0; 0
ORed with 1 is 1) which leaves the upper four bits of DDRC unchanged because they are
all ORed with the 0 (0000B) in 0x0F.
The second line of code in main() is used to switch the LED attached to PC2 on, while
leaving all other pins in their default state. In the rest of the program, the LED attached
to PC3 is left off, while the LED attached to PC2 is left on in order to demonstrate that
bitwise operators used to only switch PC0 and PC1 leave the state of the other bits
unchanged.
PORTC = PORTC | 0x04;
This line of code ORs the contents of PORTC with 0x04 (00000100B) which switches only
PC2 on and leaves all other bits in their default state.
In the while(1) loop, the bitwise AND operator is used to clear the bottom two bits of
PORTC, in other words it is used to switch LEDs attached to PB0 and PB1 off, while
leaving all other bits in PORTC in their current state.
PORTC = PORTC & 0xFC;
ANDing the contents of PORTC with 0xFC (11111100B) clears the bottom two bits of this
register and leaves the other bits unchanged. Any number ANDed with 1 will be itself
and any number ANDed with 0 is 0, as can be seen in the AND truth table. If any of the
upper bits contains a logic 1, it will be ANDed with 1 and the result will be 1. If any of the
upper bits contains a logic 0 and it is ANDed with 1, the result is 0. The lower two bits will
always be zero because they are ANDed with 0.
In the following OR operation, bit 1 is set, switching the LED attached to PC1 on. All other
LEDs and bits in the PORTC register remain unaffected.
PORTC = PORTC | 0x02;
Bit 0 in PORTC is set using the bitwise OR operator, which switches the LED attached to
pin PC0 on in the line of code below. All other bits in PORTC are unaffected, which leaves
the PC1 LED on.
PORTC = PORTC | 0x01;
In the bitwise operations in the above program, the combination of bitwise operator and
number used, changed the value of specific bits, while leaving all other bits unchanged in
the register being operated on. AND operators are used to clear bits while OR operators
are used to set bits.
In this program a read-modify-write is performed on the registers when the bitwise
operators are used. For example in the line:
PORTC = PORTC | 0x04;
The contents of the PORTC register are first read by the microcontroller – this moves the
value in the PORTC register into an internal register of the microcontroller (read). The OR
operation is then performed (modify) and the result is written back to the PORTC register
(write). There is a shorthand way of writing the above line:
● 233
C Programming with Arduino
PORTC |= 0x04;
The |= is known as a bitwise assignment operator. This operator ORs the two values at
the left and right of it together and then stores the result in the variable or register on the
left of it. Table 12-2 on page 238 shows the C assignment operators.
12.1.3 • Using the Bitwise NOT Operator
The next program shows the use of the bitwise NOT operator, also known as the
compliment operator. The not_port project uses the bitwise NOT operator to invert
(change 0s to 1s and 1s to 0s) the value written to the port and displayed on the LEDs.
The same four LEDs on pins PC0 to PC3 are used as the previous project. Create the
project without the template file.
Project name: not_port
Project location: CH12\not_port
main.c
#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
int main(void)
{
unsigned char LED_pattern = 0x0A;
DDRC = DDRC | 0x0F;
}
while(1) {
PORTC |= LED_pattern;
_delay_ms(1000);
LED_pattern = ~LED_pattern;
LED_pattern &= 0x0F;
PORTC &= 0xF0;
}
// display the pattern
// invert the LED pattern
// clear the top bits
// clear the LEDs
Program Output
The following two binary patterns are displayed on the LEDs continually with a delay in between.
1010
0101
This program writes a bit pattern to the LEDs, then uses the bitwise NOT operator to
invert the bit pattern and write the new inverted bit pattern to the LEDs.
In the while(1) loop of this program, the number 0x0A (1010B) stored in the
LED_pattern variable is written to the LEDs followed by a delay. Using the OR bitwise
assignment operator writes the number to the lower nibble of PORTC leaving the upper
nibble unchanged.
After the delay function is called, the value in LED_pattern is inverted as follows.
LED_pattern = ~LED_pattern;
● 234
Chapter 12 • Bit Manipulation and Logical Operators
This line of code inverts all of the bits in LED_pattern and stores the result back in
LED_pattern. LED_pattern is 8 bits wide and holds the number 0x0A (00001010B),
which, when inverted becomes 0xF5 (11110101B). If this number is written to PORTC
using a bitwise OR operation, the upper four bits in PORTC will be set which we do not
want. The next line of code uses the bitwise AND assignment operator to clear the upper
four bits in LED_pattern.
LED_pattern &= 0x0F;
LED_pattern now holds the number 0x05 (00000101B) which can be used to change the
lower four bits of PORTC, leaving the upper four bits unchanged.
If we now use the OR operator to OR 0x05 with PORTC, the OR operation will set bits 0
and 2, but leave bits 1 and 3 set from the first bit pattern written to the LEDs. We
therefore use the bitwise AND assignment operator to first clear the lower nibble of
PORTC while leaving the upper nibble unchanged as shown in the code below.
PORTC &= 0xF0;
After this line of code is run, program execution continues at the top of the loop where
the new inverted bit pattern is written to the LEDs. Although the above line of code
switches the LEDs off for a moment, it is too quick to see and it appears that the bit
pattern on the LEDs is inverted immediately.
12.1.4 • Using the Bitwise XOR Operator
The bitwise XOR operator can be used to toggle a bit or LED while leaving all other bits
in a register unchanged. By toggle we mean that the bit changes state. If the bit is on
or 1 and it is toggled, it changes state to off or 0. If the bit is off or 0 and it is toggled, it
changes state to on or 1.
In the xor_toggle project, a single LED is toggled using XOR to toggle the state of one
bit in a register while leaving all other bits unchanged. The LED is toggled in a loop with a
delay between toggling which results in the LED being switched on and off continuously.
Create the xor_toggle project without the use of the template file. Connect four LEDs to
pins PC0 to PC3 as done in previous projects.
Project name: xor_toggle
Project location: CH12\xor_toggle
main.c
#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
int main(void)
{
DDRC = DDRC | 0x0F;
PORTC |= 0x0C;
}
while (1) {
PORTC ^= 0x01;
_delay_ms(400);
}
● 235
C Programming with Arduino
Program Output
The following bit patterns are shown on the LEDs continuously with a delay in between:
1100
1101
In this program, pins PC0 to PC3 are set as output pins using DDRC. The top two of the
four LEDs are switched on and the bottom two LEDs are switched off by writing 0x0C to
PORTC. This number is written to the LEDs to show that the XOR bitwise operator can be
used to toggle a single bit in a register while leaving other bits in the register unchanged.
Inside the while(1) loop, the state of bit 0 in PORTC is toggled each time through the
loop followed by a delay. The XOR assignment operator toggles bit 0 of PORTC in the
following line of code, which toggles the LED attached to PC0. Other LEDs on port C are
left unchanged by the XOR operator.
PORTC ^= 0x01;
The above line of code can also be written as follows.
PORTC = PORTC ^ 0x01;
The number being XORed with PORTC is 0x01 or 00000001B. In the XOR truth table,
we can see that any bit XORed with 0 is itself. All 7 upper bits of the number being
XORed with the PORTC register are 0, so all the corresponding bits in the register remain
unchanged.
Only bit 0 in the number 0x01 has the value 1, so when 1 from the number 0x01 is
XORed with the default value of 0 in bit 0 of PORTC, the result is 1 as can be seen in the
truth table (0 XOR 1 = 1). After the XOR operation, bit 0 of the number is still 1 and bit 0
of PORTC is now 1, so the next time through the loop 1 is XORed with 1 which results in
0 from the XOR truth table (1 XOR 1 = 0). We are now back to where we started with bit
0 of PORTC equal to 0 and bit 0 of the number equal to 1. The next time through the loop
bit 0 in PORTC is changed to 1 again (0 XOR 1 = 1). This continuous XOR operation that
occurs each time through the loop results in bit 0 of PORTC being continually toggled and
the corresponding LED on pin PC0 being toggled, or switch on and off continually.
12.1.5 • The Left and Right Shift Operators
The shift operators will shift the entire contents of a variable by the specified number
of bits. The two shift operators can move the bits either to the left (<<) or to the right
(>>). If an 8-bit number is shifted by one bit to the left, every bit in the number will
move to the cell to the left of it and the LSB will be filled with 0. The MSB will be shifted
out of the number and no longer exist as it is replaced by the bit that was at the right of
it. The same number shifted to the right by one bit will result in every bit moving to the
cell to the right of it and 0 shifted into the MSB. In this case the LSB is shifted out of the
number and no longer exists.
If an 8-bit number is shifted left or right by 8 bits, all the bits will be shifted out of the
register or variable and every bit in the number will be 0. Shifting a number by two bits
will shift each bit value by two cells in the register or variable shifting two zeros into the
bottom two or top two bits, etc.
The next program uses the shift operators to create a Knight Rider display on four LEDs
connected to pins PC0 to PC3. Create the project without the template file.
● 236
Chapter 12 • Bit Manipulation and Logical Operators
Project name: knight_disp
Project location: CH12\knight_disp
main.c
#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
int main(void)
{
unsigned char move_left = 1;
unsigned char shift_val = 1;
DDRC = DDRC | 0x0F;
}
while (1) {
if (move_left) {
shift_val = shift_val << 1;
if (shift_val & 0x08)
move_left = 0;
}
else {
shift_val = shift_val >> 1;
if (shift_val & 0x01)
move_left = 1;
}
PORTC &= 0xF0;
PORTC |= shift_val;
_delay_ms(100);
}
Program Output
Four LEDs display the "Knight Rider" or running LED display.
This program uses the move_left variable as a "flag" that is used to flag when the value
displayed on the LEDs is to be left shifted and when it is to be right shifted. By shifting
a byte that has a single bit that is set, and then writing out this value to the LEDs, we
achieve the effect of an LED appearing to run to the left and then to the right – the
"Knight Rider" display.
The value to write to the LEDs is stored in the variable shift_val and is initialised to a
value of 1 i.e. this is a single byte and is initialised to the binary value of 00000001B. The
flag variable, move_left is also initialised to a value of 1 (true). When a variable is used
as a flag, it is checked in a program for being either true (contains a non-zero value) or
false (contains 0).
Upon entry into the while(1) loop, the move_left flag causes the code in the body of
the if statement to be executed as this flag evaluates to true. The value in shift_val
is shifted to the left by 1 bit and the result is stored back in shift_val. The value will
therefore have changed from 00000001B to 00000010B. A check is done after this to see
if the value 0x08 has been reached i.e. 00001000B. This check is done to see if the fourth
bit from the right of this variable is set because we are writing to only four LEDs and if
it was not done, the bit would be shifted too far to the left and would not display on the
LEDs.
The shift_val variable is written to PORTC using the bitwise OR assignment operator
so that the shifted value will be displayed on the LEDs. Just before the bitwise OR is
● 237
C Programming with Arduino
performed, a bitwise AND is used to clear the lower nibble of PORTC which turns all the
LEDs off.
After the delay function is called, the if statement is evaluated again. The shift_val
variable will be shifted left again in the body of the if statement as before until the value
becomes 0x08 (00001000B). The move_left flag will then be cleared and the body of the
else statement will be run on the next pass through the loop.
The code in the body of the else statement does exactly the same as in the if
statement, except that the value in shift_val is now shifted right by one bit and a check
is done for the value 0x01 (00000001B) before changing the move_left flag to true (1).
12.1.6 • The C Assignment Operators
There is a shorthand method for performing arithmetic and bitwise logical operations on
data in C and storing the result in one of the variables that is being operated on. We saw
this with the bitwise AND, OR and XOR operators in some of the previous projects.
In the previous example program, the following line:
shift_val = shift_val << 1;
could be changed to:
shift_val <<= 1;
Both of these lines do exactly the same thing, the second one is just a shorter way of
writing it. The value inside shift_val is moved by 1 bit to the left and the result is stored
back in shift_val.
Table 12-2 shows all of the assignment operators in C with the equivalent operations
shown in the Alternate Notation column.
Table 12-2 The Assignment Operators in C
Operator
=
+=
Usage
Alternate
Notation
A=B
A += B
Meaning
The value in B is assigned to variable A
A=A+B
A is assigned the sum of A and B
-=
A -= B
A=A–B
A is assigned the difference between A and B
*=
A *= B
A=A*B
A is assigned the value of A multiplied by B
/=
A /= B
A=A/B
A is assigned the value of A divided by B
%=
A %= B
A=A%B
A is assigned the value of the modulus of A and B
&=
A &= B
A=A&B
A is assigned the value of A ANDed with B
^=
A ^= B
A=A^B
A is assigned the value of A XORed with B
A |= B
A=A|B
A is assigned the value of A ORed with B
<<=
|=
A <<= x
A = A << x
A is assigned the result of left shifting A by x bits
>>=
A >>= x
A = A >> x
A is assigned the result of right shifting A by x bits
12.2 • Logical Operators
The logical operators in C are represented by symbols similar to the bitwise operators, but
instead of operating on single bits are used to operate on the results of expressions used
● 238
Chapter 12 • Bit Manipulation and Logical Operators
in decisions and loop statements. We will see what this means when we look at some
examples.
Table 12-3 shows the logical operators in C. From the table you should recognise the
logical NOT operator (!) as we have used it before in example programs.
Table 12-3 C Language Logical Operators
Operator
&&
Description
Example
Logical AND
||
Logical OR
!
Logical NOT
Result
A && B
True if A AND B are both true, else false
A || B
False if both A and B are false, else true
!A
True if A is false, False if a is true
The logical operators use the same truth tables as the bitwise operators in Figure 12-2
on page 230. The next example program, or_keys, shows how the logical operators are
used. Create this project using the template file.
This program sends a message out of the serial port to be displayed in the terminal
window if the ‘a’ or ‘A’ character is sent to it, and will send a different message if the ‘d’
or ‘D’ character is sent. It does not matter whether the Caps Lock key on the keyboard
is active or not as the program will perform the same action if the correct upper or lower
case character is pressed. This is achieved by using the logical OR operator (||).
Project name: or_keys
Project location: CH12\or_keys
main.c
#include <avr/io.h>
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
char rx_data;
UartInit();
}
while(1) {
if (UCSR0A & 0x80) {
rx_data = UDR0;
if (rx_data == 'a' || rx_data == 'A') {
printf("\r\nSystem Activated...\r\n");
}
else if (rx_data == 'd' || rx_data == 'D') {
printf("\r\nSystem Deactivated...\r\n");
}
}
}
Example Program Output
System Activated...
System Deactivated...
Let’s examine the if statement to see how the logical OR operator works:
● 239
C Programming with Arduino
if (rx_data == 'a' || rx_data == 'A') {
If a character other than ‘a’ or ‘A’ has been received and stored in the rx_data variable,
the if statement will evaluate each of the comparative statements which will both result
in a value of 0 or false:
if (0 || 0)
From the truth table 0 OR 0 = 0, so the if statement will evaluate to false and the code
in the body of this statement will not be run.
Should the value received and stored in rx_data be the character ‘a’, the if statement
will evaluate as follows:
if (1 || 0)
This is because the first comparative statement evaluates to true (1) as rx_data is equal
to ‘a’, and the second one evaluates to false. From the OR truth table, 1 OR 0 = 1. The if
statement will therefore evaluate to true and the code in the body of this statement will
be run. The same happens if rx_data contains the character ‘A’, from the truth table: 0
OR 1 = 1, so the if statement will also evaluate to true in this case and the code in the
body of this statement will be run.
In this program it is not possible for the combination 1 OR 1 = 1 from the truth table to
occur as the variable rx_data cannot contain both the characters ‘a’ and ‘A’ at the same
time.
The and_button program uses a mix of bitwise operators and logical operators to test if
more than one switch is pressed on the embedded system. Create this project without
the template file. LEDs are connected to port C pins PC0 to PC3 and switches are wired
to pins PC4 and PC5 as done in previous projects and shown in the circuit diagram in
appendix C.
Project name: and_button
Project location: CH12\and_button
main.c
#include <avr/io.h>
int main(void)
{
unsigned char sw_val;
DDRC |= 0x0F;
}
while(1) {
sw_val = PINC;
if ((sw_val & 0x10) && (sw_val & 0x20)) {
PORTC |= 0x0F; // switch all 4 LEDs on
}
else {
PORTC &= 0xF0; // switch all 4 LEDs off
}
}
● 240
Chapter 12 • Bit Manipulation and Logical Operators
Program Output
All four LEDs switch on only if both switches are closed.
This program enables the microcontroller to drive four LEDs on port pins PC0 to PC3 and
enables the buttons on PC4 and PC5 to be read. In the while(1) loop, the states of the
buttons are read and only if both buttons are pressed, will all four LEDs be switched on. If
either of the buttons or both are released, the four LEDs will be switched off.
In the while(1) loop, the microcontroller PINC register is read which contains the state of
the two switches or buttons that are being monitored. The value in this register is stored
in the local variable sw_val.
The if statement evaluates the state of the switches that are now captured in the sw_val
variable. The bitwise AND (&) operations are first evaluated and then these results are
evaluated together using the logical AND operator (&&).
In the if statement:
if ((sw_val & 0x10) && (sw_val & 0x20)) {
sw_val & 0x10 is evaluated first and will evaluate to either true or false depending if
the switch on PC4 is closed or not. Next, sw_val & 0x20 is evaluated to see if it is true
or false depending on if the switch on PC5 is pressed or not. These results are then
evaluated using the AND truth table with the result from this truth table allowing the code
in the body of the if statement to be run if it evaluates to true (1):
0 AND 0 = 0, no switches are closed, so the if statement evaluates to false.
0 AND 1 = 0, the switch on PC5 is closed, the if statement evaluates to false.
1 AND 0 = 0, the switch on PC4 is closed, the if statement evaluates to false.
1 AND 1 = 1, both switches are closed, the if statement evaluates to true.
In the code, the if statement was written with the bitwise operations in their own
parentheses to make the code easier to read:
if ((sw_val & 0x10) && (sw_val & 0x20)) {
The same line of code could have been written as follows, without the extra parentheses:
if (sw_val & 0x10 && sw_val & 0x20) {
This line of code works exactly the same as the line of code with the extra parentheses
but the code with the extra parentheses tends to look less confusing, and clearly shows
that the results of two bitwise AND operations are to be evaluated using the logical AND
operator.
12.3 • Operator Precedence
Operator precedence refers to the order in which expressions with multiple operators are
evaluated. For example in the following C statement:
x = 5 + 3 * 2;
Is 3 added to 5 first and then the result multiplied by 2 (x = 16)? Or is 3 multiplied by
2 and the result added to 5 (x = 11)? C follows the same rules as mathematics in this
case where the multiplication sign takes precedence over the plus sign so that 3 is first
● 241
C Programming with Arduino
multiplied by 2 and the result is added to 5 so that x is assigned the result of 11.
Table 12-4 Operator Precedence
Precedence
Operator
1
()
2
++
3
cast operator
4
*
/
5
+
-
6
<<
7
<
8
== !=
9
&
10
^
11
|
12
&&
13
||
14
? : (conditional operator)
15
=
16
, (comma operator)
[]
.
--
->
!
~
+
-
*
&
sizeof
%
>>
<=
+=
>
-=
>=
*=
/=
%=
&=
^=
|=
<<=
>>=
Table 12-4 contains the operator precedence of the C operators. The only things that
you have not seen so far that are in this table are the dot operator (.) and the structure
pointer (->).
When more that one operator is used in a C statement, they will be evaluated in the order
that they appear in this table from top to bottom, for example:
if (sw_val & 0x10 && sw_val & 0x20)
The bitwise AND operator (&) appears before the logical AND operator (&&) in the table,
so the bitwise ANDs in the above line will be evaluated before the logical AND.
Parentheses can be used to change the order of precedence as well as make it clearer
what you as the programmer are intending. As previously shown, the following line of
code does the same as the line of code above.
if ((sw_val & 0x10) && (sw_val & 0x20))
This shows that it is intended to first evaluate the bitwise AND operators as everything
inside parentheses is evaluated first.
In the same way, with our mathematical example:
x = 5 + 3 * 2;
We can write this instead:
x = 5 + (3 * 2);
This shows that we intend to multiply 3 by 2 first and then add 5 to the result before
assigning the final result to x. We can also use parentheses to change the order of
evaluation as follows:
● 242
Chapter 12 • Bit Manipulation and Logical Operators
x = (5 + 3) * 2;
This forces 5 to be added to 3 first, as the parentheses have higher precedence than the
multiplication sign. The result of 5 + 3 (8) is then multiplied by 2.
12.4 • Using Pre-defined Bit Names with Shift Operators
Header files that are included with the AVR GCC compiler have pre-defined names for bits
found in AVR registers. A program becomes easer to write and read when using bit names
instead of writing hexadecimal numbers to registers. Bit names used in conjunction with
the left shift operator and the OR and AND bitwise operators can be used to set and clear
individual bits in a register.
12.4.1 • Pre-defined Bit Names in C Code
The line of code below sets bit 2 of DDRC which configures pin PC2 as an output pin while
leaving the other bits in DDRC unchanged.
DDRC |= (1 << DDC2);
DDC2 is the name of bit number two in DDRC as found in the AVR datasheet and shown
in Figure 12-4 which shows both the DDRC and PORT register with the names of each
bit. The figure was taken from the ATmega328 datasheet which does not show the name
of bit 7 because the corresponding pin, PC7, is not present on the ATmega328. The same
registers in the ATmega2560 datasheet show bit 7 in both registers.
Figure 12-4 PORTC and DDRC from the AVR Datasheet Showing Bit Names
In the above line of code we can see that the result of a left-shift operation is assigned
to DDRC using the bitwise OR assignment operator (|=) as we have done previously. To
understand what the shift operation is doing, we need to look at what DDC2 is defined as.
TIP
Clicking on a bit name or register name in a C source code file in Atmel Studio will shown
what the bit or register name is defined as in the second box directly above the source
code. Right-clicking a bit name or register name in the C source code file will bring up a
menu. In the pop-up menu, click Goto Implementation to open up the header file where
the bit name is defined and highlight the bit name.
● 243
C Programming with Arduino
In the header file iom328p.h, the bit definitions for register bits in the ATmega328P can
be seen as shown below.
#define
#define
#define
#define
#define
#define
#define
DDC0
DDC1
DDC2
DDC3
DDC4
DDC5
DDC6
0
1
2
3
4
5
6
Substituting the defined value for DDC2 into the original line of code, which is what the C
preprocessor does, we have the following.
DDRC |= (1 << 2);
From the above it can be seen that 1 << DDC2 shifts 1 by DDC2 places to the left,
which shifts 1 by 2 places to the left. The OR assignment operator then puts the result
in DDRC. In this way bit 2 of DDRC is set, which is the same as ORing DDRC with 0x04
(00000100B). The shift operation shifts 0x01 (00000001B) by two places to the left. After
one shift to the left, the number becomes 0x02 (00000010B) and after the second shift,
the number becomes 0x04 (00000100B). It can be seen that the program is a lot easier
to understand by using the name of the bit instead of just a hexadecimal number to set
the bit.
The bit_names project uses pre-defined bit names to flash a bit pattern on two LEDs
connected to port pins PC0 and PC1. Create the project without the template file.
Project name: bit_names
Project location: CH12\bit_names
main.c
#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
int main(void)
{
DDRC |= (1 << DDC0) | (1 << DDC1);
}
while(1) {
PORTC |= (1 << PORTC0);
_delay_ms(1000);
PORTC |= (1 << PORTC1);
_delay_ms(1000);
PORTC &= ~(1 << PORTC0 | 1 << PORTC1);
_delay_ms(1000);
}
// PC0 on
// PC1 on
// PC1 and PC0 off
Program Output
The LEDs flash or blink continually in the following pattern.
01
11
00
● 244
Chapter 12 • Bit Manipulation and Logical Operators
The first line of code in main() sets bits 0 and 1 in DDRC to make pins PC0 and PC1
outputs as shown below.
DDRC |= (1 << DDC0) | (1 << DDC1);
The code does the bit shift operation as explained above, but does two bits in the same
line of code. The result of (1 << DDC0) is bitwise ORed with the result of (1 << DDC1)
the result of the OR is then ORed with DDRC. (1 << DDC0) is the same as (1 << 0)
because DDC0 is defined as 0, which means that 1 is left-shifted 0 positions resulting in 1
or 0x01 (00000001B) which is what we want for bit 0 of the register. The same line of
code could also be written as two separate statements as follows.
DDRC |= (1 << DDC0);
DDRC |= (1 << DDC1);
Using just a hexadecimal number, the equivalent code is:
DDRC |= 0x03;
Inside the while(1) loop, the two bitwise OR assignment operators in conjunction with
shift operators switch the two LEDs on.
PORTC |= (1 << PORTC0);
_delay_ms(1000);
PORTC |= (1 << PORTC1);
_delay_ms(1000);
The code switches the LED connected to PC0 on first and then the LED on PC1, with a
delay in between. In this code the left-shift and OR assignment work the same as before,
only the names of the bits change as shown in the PORTC register in Figure 12-4 on page
243.
Finally both LEDs are switched off as shown below.
PORTC &= ~(1 << PORTC0 | 1 << PORTC1);
To switch the LEDs off and leave the other bits in PORTC unchanged, we would normally
write 0xFC (11111100B) to PORTC using the AND assignment operator. The result of
the shift operations ORed together, (1 << PORTC0 | 1 << PORTC1) gives us 0x03
(00000011B) which is the inverse of what we want. The NOT operator is used to invert
0x03 to 0xFC, thus 00000011B becomes 11111100B in the above code.
12.4.2 • The AVR _BV() Macro
A macro called _BV() is defined in the header file sfr_defs.h which is included in a project
when the io.h file is included. The _BV() macro is defined as follows.
#define _BV(bit) (1 << (bit))
Including Header Files in Header Files
A header file can use #include to include other header files. The file io.h can be included
● 245
C Programming with Arduino
in an AVR C file for any AVR part number and includes the header file sfr_defs.h at
the top of io.h which contains the _BV() macro. After including sfr_defs.h, selective
preprocessor directives are used in io.h to select the correct header file that contains
register and bit definitions for the part number that was selected when creating the
project in Atmel Studio. In this way iom328p.h is included in ATmega328P projects and
iom2560.h is included in ATmega2560 projects, each with register and bit definitions for
the specific microcontroller.
The _BV() macro does a left shift of 1, just as we have done in the previous project,
except that the shift is done by the compiler at compile-time and not at run-time on the
AVR.
The following line of code uses the _BV() macro.
PORTC |= _BV(PORTC0);
In the _BV() macro, bit in _BV(bit) is substituted into bit in (1 << (bit)) at compile
time. In the above line of code, _BV(PORTC0) becomes (1 << (PORTC0)) and the result
of this left shift operation is substituted into the code.
The AVR Libc reference manual has the following to say about the _BV() macro and the
advantage of using it instead of a direct left shift:
"The bit shift is performed by the compiler which then inserts the result into the code.
Thus, there is no run-time overhead when using _BV()."
C Macros
In C #define can actually take arguments, as demonstrated by the _BV() macro,
allowing a value to be passed to #define. The ability to take an argument allows a
powerful macro to be created using ".
#define can also be used to define a C statement, for example:
#define WARN_1 printf("Warning 1\r\n")
Now, whenever WARN_1 is inserted into a C program, the C preprocessor will insert the
above printf statement. The same example can be passed a value to print out a warning
level as shown below.
#define WARN(num) printf("Warning %d\r\n", num)
In a C program, the above macro can be called as follows to print out different warning
levels. The code can be found with the accompanying files in CH12\macro_demo
WARN(1);
WARN(2);
Will print out:
Warning 1
Warning 2
● 246
Chapter 12 • Bit Manipulation and Logical Operators
Compile-time and Run-time
Compile-time refers to events or processes that the compiler takes care of when
compiling code, rather than events or processes that occur when the code is running on
the AVR.
Run-time refers to events or processes that occur when code is running on the AVR,
rather than events or processes that are handled by the compiler.
The bit_names_bv project does exactly the same as the bit_names project, but uses the
_BV() macro in place of the left-shift operations.
Project name: bit_names_bv
Project location: CH12\bit_names_bv
main.c
#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
int main(void)
{
DDRC |= _BV(DDC0) | _BV(DDC1);
}
while(1) {
PORTC |= _BV(PORTC0);
// PC0 on
_delay_ms(1000);
PORTC |= _BV(PORTC1);
// PC1 on
_delay_ms(1000);
PORTC &= ~(_BV(PORTC0) | _BV(PORTC1)); // PC1 and PC0 off
_delay_ms(1000);
}
Program Output
The same as the bit_names project.
12.5 • Exercises
12.5.1 • Print Binary Number
Use the bitwise AND operator and the shift operator to check if each bit in a byte is a 0 or
a 1. Use the results of the check to create the binary equivalent of the number and send
it to be displayed in the terminal window.
● 247
C Programming with Arduino
12.6 • Solution
12.6.1 • Solution to 12.5.1
Project created using the template file.
Project name: CH12_1_print_num
Project location: Exercises\CH12_1_print_num
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
unsigned char num = 0xA3;
unsigned char mask = 0x80;
char str[20];
unsigned char str_index;
UartInit();
/* count through each bit in the byte and build *
* a string that represents the binary number in *
* the byte
*/
for (str_index = 0; str_index < 8; str_index++) {
if (mask & num) {
/* check for 1 */
str[str_index] = '1';
/* bit is a 1 */
}
else {
str[str_index] = '0';
/* bit is a 0 */
}
mask >>= 1; /* for checking next bit in byte */
}
str[str_index] = 0;
/* null terminate the string */
printf("0x%02hX = %sB\r\n", num, str);
}
while(1);
This program uses a for loop to count through each of the bits in the byte that is to be
displayed in binary format. A variable is set up as a mask with its most significant bit set.
The program uses the mask to check whether each bit in num is a 1 or a 0 by shifting the
mask by 1 bit to the right after each check. In other words it is used to check the state of
bit 7, bit 6, bit 5, etc. and finally bit 0. A string is built up of ASCII characters ‘1’ and ‘0’
after each bit state in the number is checked. The string is null terminated when the for
loop exits and then sent out of the serial port for display in the terminal window.
12.7 • Summary
● 248
•
Bitwise operators can be used to change the state of individual bits in variables
and registers, and to check the state of bits.
•
The shift operators in C can be used to shift the contents of a variable or
register left or right by a specified number of bits.
Chapter 12 • Bit Manipulation and Logical Operators
•
Logical operators are used to combine more than one expression.
•
Operator precedence in C determines what is evaluated first. Operator
precedence can be changed by using parentheses.
•
Header files included with the AVR GCC compiler include pre-defined bits,
registers and macros that can be very useful to programmers and make
programs easier to read.
● 249
C Programming with Arduino
● 250
Chapter 13 • Programming Hardware
Chapter 13 • Programming Hardware
What you will do in this chapter:
•
Use the hardware / timer counter in the AVR to make accurate time delays
•
Read external analogue values using the analogue to digital converter
•
See how the watchdog timer works
•
Look at I/O ports in more detail
We have covered most of the C language up to now. This chapter deals with using this
knowledge to program more hardware devices on the microcontroller, namely, timers, the
watchdog timer and the analogue to digital converter (ADC). We also take a look at I/O
ports in more detail. After learning the C language, moving from one embedded system
to another or one microcontroller architecture to another is all about learning how the
new hardware works and then programming it.
Because this is a book on learning the C language, we will not cover every detail of the
microcontroller hardware, but rather demonstrate how various hardware devices are set
up and used.
13.1 • Timer Counter Hardware
The timer counters (TC) in AVR microcontrollers can be used for multiple purposes such
as event counting, pulse generation, delay timing and more. More information about
the timer/counters can be found in the AVR datasheet for the specific AVR being used.
Microcontrollers on the Arduino Uno and Arduino MEGA 2560 have the following on-chip
timer/counters.
Arduino Uno (ATmega328P)
•
Timer/Counter0, 8-bit
•
Timer/Counter1, 16-bit
•
Timer/Counter2, 8-bit
Arduino MEGA 2560 (ATmega2560)
•
Timer/Counter0, 8-bit
•
Timer/Counter1, 16-bit
•
Timer/Counter2, 8-bit
•
Timer/Counter3, 16-bit
•
Timer/Counter4, 16-bit
•
Timer/Counter5, 16-bit
A hardware timer works by feeding a clock pulse into it which causes it to count up every
clock cycle. The count value is stored in one of the timer's registers and updated by the
hardware every clock cycle. A C program can read the value in the count register which is
a 16-bit value in a 16-bit timer, or an 8-bit value if the timer is an 8-bit timer. If we had
a 1Hz clock fed into a timer, then a count would occur every 1s because 1Hz is one clock
cycle per second. A 5s time period could be generated by starting the timer and checking
when the timer reaches a count of 5. Microcontrollers usually have clock sources that
● 251
C Programming with Arduino
are a lot faster than 1Hz and are most often derived from the system clock which would
normally be in the MHz range.
If a 16MHz system clock is fed into a 16-bit timer, the timer would overflow after
approximately 4ms. An overflow occurs when the timer counts from zero to its maximum
value of 65535 and then wraps around to zero again. To get a longer time period from
a 16-bit timer, a prescaler is used to divide the system clock down into a smaller value
before being fed into the timer. The prescaler can be enabled from one of the timer /
counter registers and has a number of different values that it can divide the system clock
by.
The duration or time period of a clock cycle can be calculated using the following formula:
t=
1
f
Where :
t =time period
ƒ =clock frequency
For a 1Hz clock, the calculation is very simple:
1
= 1s
1 Hz
=
t
A 16MHz clock has a period of:
t=
=
1
f
1
16 MHz
=
1
16, 000, 000
= 0.0000000625
= 62.5 ns
A 16MHz clock has a period of 62.5 nanoseconds.
In a timer, we can easily generate a specific time period once we know how long one
clock cycle takes. The timer will increment every clock cycle or clock period, therefore:
ttimer = tperiod x count
Where :
ttimer = period generated by timer
tperiod = clock period of clock fed into timer
count = timer count
The timer can therefore time in multiples of the clock period from the clock source that it
is using.
13.1.1 • Polled Timer Time Delay
The example program called timer that follows uses timer/counter1, which is a 16-bit
timer/counter found on both the Uno and MEGA AVRs, for generating a delay period much
like the Delay() and _delay_ms() functions that we have used in previous programs.
Using the hardware timer to generate a delay is a much more accurate method than
● 252
Chapter 13 • Programming Hardware
using a software delay function. Create the project using the template file.
Project name: timer
Project location: CH13\timer
main.c
#include <avr/io.h>
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
unsigned int seconds = 0;
OCR1A = 15624;
TCCR1B = (1 << WGM12) | (1 << CS12) | (1 << CS10);
UartInit();
}
while(1) {
if (TIFR1 & (1 << OCF1A)) {
printf("Elapsed seconds = %u\r\n", ++seconds);
TIFR1 = (1 << OCF1A);
}
}
Sample Program Output
Elapsed
Elapsed
Elapsed
Elapsed
Elapsed
...
seconds
seconds
seconds
seconds
seconds
=
=
=
=
=
1
2
3
4
5
Although timer/counter 1 has nine registers, we only need to use three of these registers
to set up the timer to generate a timing delay. Figure 13-1 on page 254 shows the
three registers from the AVR datasheet used in the timer project.
In this project, the prescaler is set up to divide the system clock by 1024. The Arduino
boards with their 16MHz system clock will therefore be clocking the timer at a frequency
of 16MHz divided by 1024 which results in a 15.625kHz clock frequency. The period
of this divided clock frequency, or the amount of time for each count of the timer is
calculated as follows:
t=
1
f
1
15625
= 64 µ S
=
Each count of the timer takes 64 microseconds.
● 253
C Programming with Arduino
Figure 13-1 Timer / Counter 1 Registers Used for Polled Timing
The timer can be set up in many different modes, depending on what it is required for. In
this project the timer is set up in CTC mode (Clear Timer on Compare match) which
means that we write a count value to a register and when the timer reaches the count
value it sets a bit in a register to flag that the count has been reached and then clears its
count and starts counting from 0 again up to the specified count. The specified count that
the timer must count to before clearing itself and flagging the event is set in the OCR1A
register in the above code as follows.
OCR1A
= 15624;
OCR1A is a 16-bit register which is shown as two 8-bit registers in Figure 13-1 because
the AVR is an 8-bit microcontroller and can only access 8 bits at a time. The above C code
takes care of writing to the two 8-bit registers in one line of code.
In this line of code, the timer is set up as a one second timer for a system with a 16MHz
clock using a prescaler that divides by 1024. From the above calculation, we know that
each count of the timer will take 64µS. To calculate the number that the timer must count
up to for a 1 second time period, we divide 1 second by 64µS to get 15625 and then
subtract 1 to get 15624. The equation is shown below.
ttimer
count =
−1
tperiod
where :
ttimer = period generated by timer
tperiod = clock period of clock fed into timer
count = timer count
One is subtracted from the result because the timer takes one clock period to transition
from the value that it counts up to back to zero, which adds one extra clock period to the
timer time. We must therefore subtract one clock period or one count from the calculated
count. As an example, if the clock period was 1ms and we wanted to create a time period
of 3ms and set the count to 3, the following would happen: a count from 0 to 1 = 1ms
elapsed, count 1 to 2 = 2ms elapsed, count 2 to 3 = 3ms elapsed, count 3 back to 0 =
4ms elapsed instead of 3ms. We therefore need to subtract 1 and use a count of 2.
After setting up the count value in OCR1A, the next line of code puts the timer into CTC
mode and sets the prescaler to divide the clock by 1024 which also starts the timer.
TCCR1B = (1 << WGM12) | (1 << CS12) | (1 << CS10);
● 254
Chapter 13 • Programming Hardware
Setting bit WGM12 in TCCR1B puts the timer into CTC mode. Setting bits CS12 and CS10
in TCCR1B sets the prescaler to divide the system clock by 1024 and feeds the resulting
clock to the timer. The above settings and bit meanings are all obtained from the AVR
datasheet.
Once the timer has been set up as required and started, the code in the while(1) loop
polls the OCF1A bit in the timer's TIFR1 register to see whether the calculated time period
has elapsed.
while(1) {
if (TIFR1 & (1 << OCF1A)) {
printf("Elapsed seconds = %u\r\n", ++seconds);
TIFR1 = (1 << OCF1A);
}
}
Poll means that the software continually checks the bit to see whether it has been set
– this is done in the above if statement. The OCF1A bit will be set when the timer has
counted up to the value set in OCR1A which signals that the timer has reached the set
timer period of 1 second in this example. When the OCF1A bit is set, the current number
of elapsed seconds in the seconds variable is incremented and printed to the terminal
window. The OCF1A bit must be cleared by writing 1 to it in the TIFR1 register so that the
if statement does not immediately evaluate to true the next time through the while(1)
loop. The timer will start counting from 0 again and set the OCF1A bit after another
second has elapsed.
Polled vs. Interrupt Driven Hardware
Polled hardware or polling hardware means that software is used to continually check a
hardware device's register for a change of state that signals that the hardware needs to
be serviced by the software. Servicing the hardware could mean reading a received byte
from a UART, or performing some action after a timer has timed out.
Interrupt driven hardware is an alternative to polling hardware and uses a
microcontroller's interrupt system to automatically run a function, called an interrupt
service routine or ISR, when a particular hardware event occurs. The advantage of
interrupt driven hardware is that the microcontroller does not spend time polling a
register which frees up the microcontroller's processing time. Interrupts are explained in
Chapter 15.
13.1.2 • Hardware Timer Function
In the next project called hw_timer, timer / counter 1 is used in the same mode as the
previous project to create a hardware timer function. A function called TC1Delay() uses
timer/counter 1 to generate a delay of 1ms which it uses to create time delays that are
multiples of 1ms.
Create this project without using the template file. Connect an LED to port C pin PC0 as
done before. The LED is flashed on and off to demonstrate the time delay.
● 255
C Programming with Arduino
Project name: hw_timer
Project location: CH13\hw_timer
main.c
#include <avr/io.h>
void TC1Delay(unsigned long ms_delay);
int main(void)
{
DDRC |= (1 << DDC0);
}
while(1) {
PORTC ^= (1 << PORTC0);
TC1Delay(500);
}
// LED on PC0
// toggle LED
// 500ms delay
void TC1Delay(unsigned long ms_delay)
{
OCR1A = 15999;
TCCR1B = (1 << WGM12) | (1 << CS10);
}
while(ms_delay) {
if (TIFR1 & (1 << OCF1A)) {
TIFR1 = (1 << OCF1A);
ms_delay--;
}
}
// stop timer
TCCR1B = 0x00;
Program Output
LED on PC0 flashes on and off every second – 500ms on, 500ms off.
TC1Delay() sets up timer / counter 1 with no prescaler value, which supplies the timer
with the system clock of 16MHz. The value written to OCR1A sets the timer time period
to 1ms. The value passed to variable ms_delay in the timer function is used to create a
timer period that is a multiple of the timer / counter 1 period of 1ms.
13.2 • The Analogue to Digital Converter (ADC)
The ADC converts an external analogue signal or voltage level placed on one of the AVR's
ADC pins to a digital number that the microcontroller can read and store in a variable. A
reference voltage used by the ADC allows us to calculate how many volts each count in
the number read from the ADC represents. The voltage of the external analogue signal
can be calculated by multiplying the number read from the ADC by the voltage that each
count in the number represents.
The external voltage applied to an ADC pin must be less than or equal to the reference
voltage. On the Arduino Uno and MEGA, the default ADC reference voltage used is the
5V power supply voltage which comes from either the USB 5V or 5V from the regulator
circuit when using an external power supply. A pin called AREF on the AVR microcontroller
is connected to one of the pin headers of the Arduino where it is also called AREF. The
AREF pin allows an external reference voltage to be used with the ADC.
● 256
Chapter 13 • Programming Hardware
ADC resolution is the number of bits that make up the digital number that represents the
analogue value or signal being measured. Both the ATmega328P and the ATmega2560
have 10-bit ADCs which means that the biggest number that can be read from the ADC is
1023 (210 – 1). This number represents the maximum voltage that can be applied to an
ADC input pin and is the same voltage as the ADC reference voltage AREF. In other words
if a value of 1023 is read from the ADC, we know that the voltage being measured is the
same as the reference voltage.
To calculate the voltage that each count of the ADC represents, we divide the reference
voltage by 210 as shown in the following calculation.
The voltage of the analogue voltage on an analogue pin can be calculated as follows.
In the above equation, count is the number read from the ADC in software.
The above two steps can be simplified into the single equation:
Example with ADC count value read from ADC equal to 300 and reference voltage of 5V:
The Arduino boards / AVR microcontrollers have the following ADC features.
Arduino Uno (ATmega328P)
•
10-bit resolution ADC
•
Multiplexed ADC channels
•
6 ADC channels ADC0 to ADC5 on the AVR mapped to Arduino pins A0 to A5
•
All ADC pins are found on port C pins PC0 to PC5
•
On-chip temperature sensor on ADC8
•
ADC reference voltage selectable between VCC, VREF or internal 1.1V reference
Arduino MEGA 2560 (ATmega2560)
•
10-bit resolution ADC
•
Multiplexed ADC channels
● 257
C Programming with Arduino
•
16 ADC channels ADC0 to ADC15 on the AVR mapped to Arduino pins A0 to
A15
•
ADC pins are found on port F pins PF0 to PF7, and port K pins PK0 to PK7
•
ADC reference voltage selectable between VCC, VREF or internal 1.1V or 2.56V
references
The analogue project reads the voltage on ADC channel 0, which is connected to Arduino
pin A0, and displays the ADC count value as well as the calculated voltage for the voltage
on A0. Create this project using the template file and use the terminal program to see the
output from the ADC.
To change the voltage on A0, a potentiometer can be used with one outer pin connected
to GND, the other outer pin connected to 5V and the centre wiper connected to A0.
Alternatively, a jumper wire can be used to connect A0 to GND to see the ADC display 0,
to the Arduino 3.3V pin to see a value of about 676 and to 5V to see the maximum ADC
value of 1023.
Project name: analogue
Project location: CH13\analogue
main.c
#define F_CPU 16000000UL
#include
#include
#include
#include
<avr/io.h>
<util/delay.h>
<stdio.h>
"stdio_setup.h"
int main(void)
{
unsigned int ADC_data;
float ADC_voltage;
ADMUX = (1 << REFS0); // 5V supply used for ADC reference, select ADC channel 0
DIDR0 = (1 << ADC0D); // disable digital input on ADC0 pin
// enable ADC, start ADC, ADC clock = 16MHz / 128 = 125kHz
ADCSRA = (1 << ADEN) | (1 << ADSC) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
UartInit();
}
while(1) {
if (!(ADCSRA & (1 << ADSC))) { // check if ADC conversion is complete
ADC_data = ADC;
// get the ADC value
ADC_voltage = 5.0 * ADC_data / 1024.0;
// calculate the ADC voltage
printf("ADC = %4d 0x%03x %0.3f\r\n", ADC_data, ADC_data, ADC_voltage);
ADCSRA |= (1 << ADSC);
// start the next ADC conversion
_delay_ms(1000);
}
}
Example Program Output
ADC = 482 0x1e2 2.354
ADC = 483 0x1e3 2.358
...
● 258
Chapter 13 • Programming Hardware
Again, it is essential to look at the datasheet to see which registers need to be used to set
up the ADC and what the meaning of each bit in the registers is.
The ADMUX register is used to select the voltage reference used with the ADC, and which
ADC channel to read a value from. Selectable voltage references are AREF from the
AREF pin, AVCC which is the 5V supply on the Arduino, or the 1.1V internal reference.
The Arduino MEGA can also select a 2.56V internal reference. In the above code, AVCC is
selected which uses the 5V supply as the ADC reference voltage.
ADMUX is also used to select which analogue channel to read an analogue value from and
convert to a digital value. In the code, ADC0 is selected by setting the lower four bits of
ADMUX to 0. The ADC has multiplexed analogue inputs which means that only one of the
analogue input pins can be read by the ADC at a time because all of the ADC channel pins
share the same ADC. To read analogue values from two different channels, one channel
must be selected and the analogue to digital conversion done. The second channel
must then be selected and the analogue to digital conversion done on it. In this way the
analogue input channels are time-shared by the ADC.
Pins on AVR microcontrollers can be set up as inputs, outputs or used for an alternative
function such as an ADC input. When a pin is used as an ADC channel input, the digital
input circuitry is no longer needed on the pin. This digital input circuitry can optionally be
disabled which will save some power on the AVR. Digital Input Disable Register 0 (DIDR0)
is used to disable the input buffer circuitry of the ADC0 pin in the following line of code.
DIDR0 = (1 << ADC0D);
Before entering the while(1) loop, the ADC clock source is selected, the ADC is enabled
and started using the code below.
ADCSRA = (1 << ADEN) | (1 << ADSC) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
Bits ADPS0 to ADPS2 select the ADC prescaler division factor to divide the system clock
by 128. This divides the 16MHz Arduino system clock by 128 to get 125kHz which is used
as the ADC clock. The ADEN bit is used to enable the ADC and the ADSC bit is used to
start the ADC conversion.
The if statement in the while(1) loop checks to see when the ADC has finished
converting the analogue value to a digital value. When the conversion is complete, the
ADSC bit in ADCSRA changes state from logic 1 to 0. The if statement uses the NOT
operator so that the expression evaluates to true when the ADSC bit is 0 or false as
shown in the code below.
if (!(ADCSRA & (1 << ADSC))) {
When the ADC conversion is complete, the ADC digital value found in the ADC register is
saved to the ADC_data variable.
ADC_data = ADC;
The voltage on ADC0 or Arduino pin A0 is calculated using the equation shown earlier in
this section which looks as follows in C.
ADC_voltage = 5.0 * ADC_data / 1024.0;
● 259
C Programming with Arduino
printf() is used to print out the raw ADC value in decimal and hexadecimal, and also
to print out the calculated voltage from the above equation. After printing out the ADC
value, the ADC conversion is started again to convert the voltage on ADC0 again. A delay
after starting the next ADC conversion prevents too many consecutive messages being
printed to the terminal program. Polling of the hardware was used again in this ADC code
by continually checking the state of a bit in the if statement of the program.
13.3 • The Watchdog Timer
A watchdog timer is a timer that is started at the beginning of a program and reset
periodically in the program to prevent it from timing out. If the software program should
hang up then it will not be able to reset the watchdog timer which will then time out.
When the watchdog times out it will reset the microcontroller, breaking the program out
of the locked up state.
Create the watchdog project using the template file to see how the AVR watchdog timer
works.
Project name: watchdog
Project location: CH13\watchdog
main.c
#define
#include
#include
#include
#include
F_CPU 16000000UL
<avr/wdt.h>
<util/delay.h>
<stdio.h>
"stdio_setup.h"
int main(void)
{
unsigned int count = 0;
wdt_enable(WDTO_8S);
UartInit();
printf("\r\n\r\n** Beginning of program **\r\n");
printf("Entering loop that resets watchdog...\r\n\r\n");
/* watchdog timer is reset in this loop */
while(1) {
_delay_ms(1000);
printf("\rcount = %4d", count++);
wdt_reset();
if (count > 20) {
count = 0;
break;
}
}
}
printf("\r\n\r\nNow not resetting watchdog...\r\n");
printf("Expect the microcontroller to be reset...\r\n\r\n");
/* watchdog timer is not reset in this loop */
while (1) {
_delay_ms(1000);
printf("\rcount = %4d", count++);
}
● 260
Chapter 13 • Programming Hardware
Sample Program Output
** Beginning of program **
Entering loop that resets watchdog...
count =
20
Now not resetting watchdog...
Expect the microcontroller to be reset...
count =
7
** Beginning of program **
Entering loop that resets watchdog...
count =
1
At the beginning of the watchdog program, the watchdog timer is enabled with an 8
second time-out value as shown in the following line of code.
wdt_enable(WDTO_8S);
In order to use the watchdog timer in a C program, the header file wdt.h must be
included in the C source code file. When the wdt.h file is included, then the watchdog
functions and definitions are available in the program. The watchdog timer has its own
internal oscillator running at 128kHz and uses a prescaler to set its time-out period.
Below are the various time-out periods in milliseconds and seconds that the watchdog can
be set to using the wdt_enable() function.
WDTO_15MS
WDTO_30MS
WDTO_60MS
WDTO_120MS
WDTO_250MS
WDTO_500MS
WDTO_1S
WDTO_2S
WDTO_4S
WDTO_8S
After enabling the watchdog timer, the program prints some messages to the terminal
window and then enters the first while(1) loop. Inside the first while(1) loop a count
value is printed every second and then incremented. The watchdog timer is reset by
calling the wdt_reset() function so that the watchdog will not time out and reset the
microcontroller. When the value of the count becomes greater than 20, the first while(1)
loop is exited using the break statement.
The second while(1) loop simulates what will happen if the software hangs up or gets
stuck in an endless loop that does not reset the watchdog timer. Code in the second
while(1) loop prints the count value and increments it exactly the same way as the code
in the first loop did, but it never resets the watchdog timer. After the 8 second watchdog
time period has elapsed, the watchdog timer resets the microcontroller which can be seen
when the initial messages at the beginning of the program are printed to the terminal
window again. The program then starts running from the beginning again.
● 261
C Programming with Arduino
13.4 • I/O Ports
Each I/O port of an 8-bit AVR microcontroller has its own set of three registers called the
data direction register (DDR), PORT register and PIN register. In this section we look at
the I/O ports in more detail which shows additional functionality available when using the
three port registers. Additional functionality includes enabling internal pull-up resistors on
the port pins and the ability to toggle the state of a port pin using a register.
We have been using port C of the AVR microcontroller on the Arduino Uno and Arduino
MEGA boards because both these boards have pins from port C that are accessible on
headers. When the I/O port registers are used in C programs, their names are followed by
the letter of the port being used. When using port C, the register names become DDRC,
PORTC and PINC.
13.4.1 • I/O Port Register Functionality
Setting up Pins as Inputs and Outputs
The DDR register sets a port pin up as an output when a logic 1 is written to the
corresponding bit in this register. At power up, all bits in DDR are set to logic 0 and
therefore all pins are set up as inputs.
Changing the State of a Pin
When port pins are set up as outputs and the PORT register is written to, the logic level
written to this register appears on the corresponding pin, i.e. writing logic level 1 to a bit
in the PORT register drives the corresponding pin high if the pin is set up as an output
pin. Writing a logic level of 0 to a bit in the PORT register drives the corresponding pin low
if the pin is set up as an output.
Enabling the Pull-up Resistor on a Pin
Each pin on an AVR port has a pull-up resistor that can be enabled to pull the pin high
when it is set up as an input. When port pins are set up as inputs and the corresponding
bits in the PORT register are set to logic level 1, pull-up resistors on these pins are
enabled. Conversely when port pins are set up as inputs and the corresponding bits in the
PORT register are set to logic level 0, pull-up resistors are disabled on these pins.
Reading the State of an Input Pin
The state of an input pin on a port is found by reading the PIN register and testing the
state of the corresponding bit from the PIN register.
Using a Register to Toggle a Pin
Writing a logic 1 to a bit in the PIN register toggles the corresponding bit in the PORT
register and the state of the corresponding pin if it is set up as an output pin.
13.4.2 • Register Pin Toggle
The reg_toggle program shows how to toggle the state of a pin using the PIN register as
described above. Create the project without the template file. An LED connected to port C
pin 0 is used in this program.
● 262
Chapter 13 • Programming Hardware
Project name: reg_toggle
Project location: CH13\reg_toggle
main.c
#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
int main(void)
{
DDRC |= (1 << DDC0);
while (1) {
PINC = (1 << PINC0);
_delay_ms(500);
}
}
// LED on pin PC0
// toggle the LED
Program Output
LED on pin PC0 toggles every 500ms.
In this program it can be seen that writing a 1 to PINC toggles the LED on PC0. A 0 is
never written to the register and no bitwise operator is used.
13.4.3 • Enabling Pull-up Resistors
In the pull_up project below the internal pull-up resistor is enabled on port C pin PC4
which allows a switch to be connected directly between the port pin and GND without any
external resistor needed.
Figure 13-2 shows the circuit diagram used for the pull_up project. Create the project
without using the template file.
Figure 13-2 Circuit Diagrams for the pull_up Project for the Uno and MEGA
● 263
C Programming with Arduino
Project name: pull_up
Project location: CH13\pull_up
main.c
#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
int main(void)
{
DDRC = (1 << DDC0);
PORTC = (1 << PORTC4);
}
// LED on PC0, all other pins = inputs
// enable pull-up on PC0
while (1) {
if (PINC & (1 << PINC4)) {
// switch is open
PORTC &= ~(1 << PORTC0);
}
else {
// switch is closed
PORTC ^= (1 << PORTC0);
_delay_ms(100);
}
}
// switch LED off
// flash LED
Program Output
The LED on PC0 remains off when the switch is open.
The LED flashes on and off when the switch is closed.
At the beginning of this program bit DDC0 in DDRC is set to logic 1 to set pin PC0 as
an output. At the same time logic 0 is written to all of the other bits in DDRC making
them inputs. Logic 1 is written to bit PORTC4 in the PORTC register to enable the pull-up
resistor on pin PC4.
In the while(1) loop, bit PINC4 in the PINC register is checked to see whether it is high
or logic 1 which indicates that the switch is open. If the switch is open, the LED on PC0 is
switched off. If the switch is closed then the if statement will evaluate to false and the
code in the else block that flashes the LED on and off will run.
Note that the switch logic has been reversed from when an external pull-down resistor
was used in all of the previous programs that used a switch. Because the internal pull-up
resistor is used, when nothing, or an open switch is connected to the pin, a logic 1 will be
read on the pin because the resistor pulls the pin high. When the switch is closed, the pin
is connected to GND causing the pin state to change to logic 0.
If a program needed to perform a task only when the switch is closed then the logical
NOT operator could be used in the if statement to invert the logic as the following line of
code shows.
if (!(PINC & (1 << PINC4))) {
In the above line, code in the if block will run when the switch is closed if the switch is
configured with a pull-up resistor.
● 264
Chapter 13 • Programming Hardware
13.5 • Summary
•
In order to program an embedded system in any language, knowledge of the
hardware being programmed is required.
•
Information on a microcontroller's internal hardware is found in the datasheet
for the microcontroller.
•
Information on an embedded system's hardware is found in the circuit diagram
for the hardware and the embedded system manufacturer's documentation.
•
The timer/counter in AVR microcontrollers can be used to generate accurate
timing intervals.
•
An analogue to digital converter (ADC) is used to read external analogue values
and convert them into digital numbers.
•
Polling hardware refers to continually checking the hardware in a program to
see if an event has occurred.
•
A watchdog timer is used to recover from software that hangs up a
microcontroller by resetting the microcontroller.
•
AVR I/O port registers can be used to toggle the state of a pin and enable
internal pull-up resistors on input pins in addition to normal I/O pin
configuration and operation.
● 265
C Programming with Arduino
● 266
Chapter 14 • Debugging
Chapter 14 • Debugging
What you will do in this chapter:
•
Use the AVR Dragon or simulator in conjunction with Atmel Studio to debug a
program
•
Single-step through source code in Atmel Studio
•
Look inside variables, registers and memory using the debugging tools
An AVR Dragon used with Atmel Studio can single-step through C code in a project in
order to find program errors as part of the debugging process. Contents of variables,
memory and registers can be examined using the AVR Dragon. If you do not have an
AVR Dragon, the AVR simulator can be used instead, but changes to externally interfaced
hardware like LEDs switching on can not be seen with the simulator.
14.1 • Configuring Arduino Boards for Debugging
AVR microcontrollers on the Arduino Uno and MEGA have an on-chip debug system called
debugWIRE that interfaces debugging software to the AVR through a device such as the
AVR Dragon connected to the ICSP header. In order for debugWIRE to work on the AVR
on the Arduino boards, reset circuitry must be disconnected from the reset pin. Modern
Arduino boards have a solder link labelled RESET-EN that consists of two solder pads
connected by a circuit board track. The track must be cut through in order to disconnect
part of the reset circuitry from the reset pin when using debugWIRE. The connection can
be remade by soldering the two solder pads together.
Figure 14-1 Location of the RESET-EN Solder Pads on the Arduino MEGA 2560
(left) and Arduino Uno (right)
Figure 14-1 shows where the RESET-EN solder pads are found. Use a sharp blade from
a craft knife or scalpel to cut through the track that joins the two solder pads. Be very
careful not to slip and cut other tracks on the board. It is usual to make several cuts on
the track to try to remove the track so that the pads are properly disconnected.
14.2 • Debugging Example Program
Create the debug program below by starting a new Atmel Studio GCC C executable
● 267
C Programming with Arduino
project from scratch without the template file. This project is used to demonstrate how to
use the debugger. Use the circuit from Appendix C page 335 for this project.
Project name: debug
Project location: CH14\debug
main.c
#include <avr/io.h>
void Delay(void);
int main(void)
{
int var = 1;
int result, val1, val2;
float flt_res;
unsigned char LED_val = 0;
DDRC |= 0x0F;
PORTC |= 0x0A;
// four LEDs
// switch two LEDs on
val1 = 25;
val2 = 39;
result = val1 * val2;
result /= 100;
flt_res = result / 100.0;
while (!(PINC & (1 << PINC4)));
}
while (1) {
if (PINC & 1 << PINC5) {
LED_val = 0;
}
PORTC &= 0xF0;
PORTC |= LED_val;
LED_val++;
if (LED_val > 0x0F) {
LED_val = 0;
}
Delay();
var++;
}
// wait for switch press
// reset count
// display count on LEDs
// keep count within range
void Delay(void)
{
volatile unsigned long del = 100000;
}
while (del) {
del = del - 1;
}
Program Output
1010 is displayed on the LEDs followed by some calculations that can only be seen by using
the debugger.
When the button on pin PC4 is pressed a count is displayed on the LEDs.
Pushing the button on pin PC5 resets the count switching all the LEDs off.
● 268
Chapter 14 • Debugging
14.3 • Switching Compiler Optimisation Off
Before compiling the above program, we will switch compiler optimisation off because
optimisation tends to optimise certain lines of code away making it impossible to step to
certain lines of code or view some of the variables when debugging.
To switch compiler optimisation off, click Project → debug Properties on the top menu,
where debug is the name of the currently open project. In the page that opens, click
Toolchain on the left menu and then Optimization under AVR/GNU C Compiler as
shown in Figure 14-2. In the Optimization Level drop-down box, select None (-O0).
Click the save button on the toolbar or Ctrl + S on the keyboard to save the changes. The
project can now be built.
Figure 14-2 Switching Compiler Optimisation Off
14.4 • Enabling debugWIRE on the AVR
The default programming mode of the AVR is ISP mode which is used for programming
the device only. In order to use the debugging capabilities of the AVR, it must be put into
debugWIRE mode which uses the same ICSP header on the Arduino board as ISP mode.
To enable debugWIRE mode, a single AVR fuse bit called DWEN must be set. No other
changes need to be made to the hardware – the AVR Dragon remains plugged into the
Arduino ICSP header as before.
To set the DWEN fuse bit and enable debugWIRE mode, click the Device Programming
icon on the top toolbar or click Tools → Device Programming on the top menu bar. In
the Device Programming dialog box that pops up, make sure that the tool selected is the
AVR Dragon and that the interface is set to ISP, then click the Apply button. The Read
button under Device signature can be clicked to verify that the AVR Dragon and AVR on
the Arduino board can be accessed by reading back the device signature and displaying it
in the dialog box.
In the left panel of the Device Programming dialog box, click Fuses and then click the
DWEN checkbox as shown in Figure 14-3 on page 270. Finally click the Program button
so that the fuse is set in the Arduino, and then close the dialog box.
Now that the AVR is in debugWIRE mode, ISP mode can no longer be used until the
device is switched back to ISP mode. For this reason we need to select debugWIRE as the
interface for the AVR Dragon.
Click the No Tool button on the toolbar; or on the top menu bar, click Project →
<project name> Properties and then click Tool in the left menu of the page that opens.
Select AVR Dragon and debugWIRE on the page as shown in Figure 14-4 on page 270
and then save the settings.
● 269
C Programming with Arduino
Figure 14-3 Programming the DWEN Fuse to Put the AVR into debugWIRE Mode
Once the AVR is using the debugWIRE interface, it can be programmed and debugged as
much as needed when doing software development work with the AVR. Only when the
development and debugging is finished does the AVR need to be put back into its default
ISP mode which is explained in section 14.6 on page 277.
Figure 14-4 Selecting the AVR Dragon and the debugWIRE Interface
● 270
Chapter 14 • Debugging
14.5 • Using the Debugger
This section contains a series of exercises that go through various capabilities of the
debugger in Atmel Studio using the above debug project.
14.5.1 • Starting the Debugger
Click the Start Debugging and Break icon on the toolbar, or click Debug → Start
Debugging and Break on the top menu bar. This will start the debugger which will then
stop the C program at the beginning of main(), allowing us to single step through each
line of code from the beginning of the program. A yellow arrow appears at the left of the
current point of execution in the code and the line of code that will be executed next is
highlighted.
14.5.2 • Single Stepping and Adding Watches
Click the Step Over icon on the toolbar (Debug → Step Over on the top menu) or press
F10 on the keyboard to step to the first line of code in the project. Click the Step Over
icon or press F10 again to execute the following line of code which initialises the variable
var with a value of 1.
int var = 1;
Move the mouse cursor over the variable name in the code and a box will pop up showing
the value that the variable has been initialised with. The debugger steps over the next
two lines of code because these lines of code only define variables without initialising
them.
Variables can be "watched" by adding them to the watch window that is found at the
bottom left of Atmel Studio when debugging. Right click var in the code and then click
Add Watch on the pop-up menu. This adds the variable to the bottom watch window and
shows the value that it contains.
The value that the variable contains will be updated in the watch window as it is changed
in the program when we step over a line of code that modifies the variable. Use the
same method to add the variables result, flt_res and LED_val to the watch window as
shown in Figure 14-5 on page 272.
14.5.3 • Viewing Hardware Registers
To view the registers of internal microcontroller hardware peripherals, click Debug
→ Windows → I/O on the top menu which will open a floating window displaying
microcontroller peripherals. There is an I/O icon on the toolbar, but it is easier to find
using the top menu bar at first.
Click PORTC in the I/O window to display the port C registers in the bottom section of the
window as shown in Figure 14-6 on page 272.
Step over the line of code that sets the lower four bits of DDRC and notice that DDRC in
the I/O window changes value from 0x00 to 0x0F. Individual bits displayed as squares
next to DDRC in the I/O window change to solid red squares indicating that they have
changed to logic 1 values. The lower four of these bits change to solid red squares while
the upper four bits remain unchanged as we would expect.
● 271
C Programming with Arduino
Figure 14-5 Variables Added to the Watch Window
Stepping over the line of code that writes to the PORTC register switches two of the four
LEDs attached to PORTC on and the same 1010B bit pattern is displayed next to PORTC in
the I/O window. The previously changed bits in DDRC now change to solid blue squares
and two bits in PORTC change to solid red squares in the I/O window. Solid red squares
show the most recent changes to the register and that the values are logic level 1.
Figure 14-6 Displaying the AVR Hardware Registers
● 272
Chapter 14 • Debugging
14.5.4 • Viewing and Modifying Variable and Register Values
Step over three more lines of code so that the line of code containing the multiplication
of two variables is executed. Notice that the value of variable result changes and is
highlighted in red in the watch window. Again, red indicates that the value has changed
after the most recent step through the code. Stepping over the next line of code that
divides result by 100 changes the value in result again so that it is highlighted in red
in the watch window. Step over the next line of code that divides result by 100.0 and
stores the value of the calculation in flt_res and the value displayed next to result in
the watch window changes from red back to black. The value of any variable can also be
viewed by hovering the mouse cursor over the variable in the code while debugging.
Figure 14-7 Manually Changed Variable Value using the Debugger
Variables can be modified in the watch window by double-clicking the value of the
variable in the watch window, typing a new value and pressing the Enter key as shown in
Figure 14-7 where the value of result is manually changed from 9 to 27.
Manually changing the value of a variable can be useful during debugging when a variable
does not contain the value expected. The variable can then be manually changed in order
to further test the code without having to make changes to the code, recompile and
reload the program.
Register values can also be changed in the debugger. In the I/O window, click the LSB
block in the PORTC register and it will change from logic 0 to logic 1 switching on the LED
connected to pin PC0. Clicking the block again will change it from solid blue to a block
with a blue outline representing logic 0, at the same time the LED will switch off. The
entire value in a register can be changed in the same way as a variable value by doubleclicking the value in the I/O window, typing in a new value and pressing Enter.
14.5.5 • Viewing Variables in Memory
Memory can be viewed in the memory window of the debugger, which shows the
addresses of bytes in memory, the hexadecimal value of the contents of the memory
bytes and the ASCII value of the contents of the memory bytes.
Addresses of variables can be seen in the watch window under the Type heading – they
appear as a hexadecimal number next to the @ symbol as can be seen in Figure 14-7. In
the figure, the variable var can be seen to have the address 0x08ed.
To find a variable in memory, use the memory window at the bottom right of Atmel
Studio. Select data IRAM in the Memory selection box and then type the address of the
variable in the Address box as shown in Figure 14-8 on page 274.
Figure 14-8 on page 274 shows two views of the watch window and memory window.
The top image in the figure shows the variables in decimal format and the corresponding
SRAM memory contents set to display the memory bytes in four columns. Click the small
● 273
C Programming with Arduino
button to the very right of the address box to pop out a box that allows the memory to be
displayed in different columns.
The bottom image in Figure 14-8 shows the variable values displayed in the watch
window in hexadecimal format and the value of variable result changed to 0xaabb
to help show what it looks like in memory. Right click in the watch window and click
Hexadecimal Display in the pop-up dialog box to display variable values in hexadecimal.
In the memory window, the address at the top has been changed to 0x08F4, which is the
address of variable result. The columns have been set to two bytes wide. It can now be
seen how the contents of variable result have been stored in memory. The low byte of
the two-byte wide integer result is stored at the address 0x08F4 and contains 0xbb. The
high byte of result is stored at the next highest memory location, 0x08F5 and contains
0xaa. This order of byte arrangement is called little-endian where the least significant
byte (LSB) is stored at the smallest address and the most significant byte (MSB) is stored
at the biggest byte address. Big-endian storage stores the MSB at the smallest address
and the LSB at the biggest address.
Although an 8-bit microcontroller does not have endianness because it accesses a byte at
a time, the compiler must use either little-endian or big-endian storage for variables that
are larger than one byte. The choice of little or big-endian for the compiler will probably
be influenced by how the internal registers are ordered when a 16-bit value is stored as
two bytes – a high byte and a low-byte pair. Internal 8-bit AVR processor registers and
hardware registers are ordered in little-endian format when they are grouped as two 8-bit
registers to make up a 16-bit register.
This exercise of looking at a variable in memory should further help to visualise how
variables are stored in memory. In C, the value of a variable is accessed using the
variable name, which is the equivalent of the contents of a memory location. Each
variable has an address which is the address of the memory that holds the variable’s
contents.
Figure 14-8 Viewing Variables in Memory
14.5.6 • Stopping Debugging and Setting Breakpoints
Stop the debugging session by clicking the Stop Debugging icon on the toolbar or click
Debug → Stop Debugging on the top menu bar. We will now set a breakpoint in the code
and start debugging again.
A breakpoint allows a program to be run without single-stepping until the line of code
that is marked with the breakpoint is reached. When the breakpoint is reached, program
execution is stopped at the breakpoint allowing the debugger to be used to single-step
through code again or to view variables and memory.
● 274
Chapter 14 • Debugging
Set a breakpoint by clicking in the grey bar to the very left of a line of code, or by rightclicking a line of code and then selecting Breakpoint → Insert Breakpoint from the popup menu. Set a breakpoint at the line of code that does the floating point division as
shown in Figure 14-9.
The debugger can now be started by clicking the Start Debugging icon on the toolbar
or Debug → Continue from the top menu bar. The program will now run until the
breakpoint is reached, after which control is handed over to the Atmel Studio debugger.
Breakpoints are often used to check if a certain line of code is reached which may not
happen if there is a bug in the program. The breakpoint can then be moved to an earlier
line of code to see if it is executed.
To remove a breakpoint, simple click the red circle to the left of the line of code that
contains the breakpoint or right-click the line of code and choose Breakpoint → Delete
Breakpoint from the pop-up menu.
To start the debugger and run to a certain line of code, an alternative to using a
breakpoint is to use Run To Cursor. Click on a line of code to move the cursor to it and
then click the Run To Cursor icon on the toolbar or Debug → Run To Cursor from the
top menu.
After running to the breakpoint, step over the line of code that does the floating point
division. The breakpoint can now be removed.
Figure 14-9 Setting a Breakpoint in Atmel Studio
14.5.7 • Viewing Pin State Changes
The next line of code to step over is shown below and waits for the push-button switch
connected to pin PC4 to be closed.
while (!(PINC & (1 << PINC4)));
// wait for switch press
Open the I/O window again and click PORTC to view the contents of the PINC register.
Step over the above line of code to run it. The code runs and waits for the push-button
switch to be pressed – press the button to move to the next line of code. After pressing
● 275
C Programming with Arduino
the button, the debugger can step to the next line of code and control is handed back
to the debugger software. The state of bit 4 in the PINC register can be seen in the I/O
window as a solid red block because the value of the bit changed from 0 to 1 when the
switch was closed. Step over the next line of code and bit 4 in PINC is shown as a block
with a red outline, indicating that its value changed from 1 to 0. Holding the push-button
switch closed during any step will change the value of the corresponding bit in PINC in the
I/O window. Releasing the switch and then stepping through the code again will update
the state of the corresponding bit with logic 0.
14.5.8 • Viewing Variable Value Changes & Setting Conditional Breakpoints
Step through the code several times to see the values of variables LED_val and var
change in the watch window as they are incremented by the code. When the LEDs are
written to using bitwise AND and OR operators, it can be seen that the AND operation
clears the LEDs and the OR operation switches LEDs on. The fact that the LEDs switch off
for a very short period of time would not normally be visible when the program is running
at full speed, but can be seen when using the debugger.
A conditional breakpoint can be set that will cause program execution to stop at the
breakpoint only when a certain condition is met. We will set a breakpoint on variable var
so that when it reaches a value of 10, program execution will stop.
Figure 14-10 Setting a Conditional Breakpoint
Set a breakpoint next to the line of code that increments var as shown in
Figure 14-10. In the lower right window where we viewed memory, click the Breakpoints
tab at the bottom of the window. Right-click the breakpoint in the bottom right window
and select Settings... from the pop-up menu. In the Breakpoint Settings dialog box,
check the Conditions box as shown at the top of Figure 14-10. Next to Conditional
Expression, select Is true so that when the conditional expression that we will create
next is true, program execution will stop at the breakpoint. A condition to break on must
now be set – click the box to the right of Is true and enter var == 10 which will cause
● 276
Chapter 14 • Debugging
the program to halt when the variable var contains a value of 10. Click the OK button
in the dialog box when done. Run the program by first stopping the debugger using the
Stop Debugging button on the toolbar, and then starting it again by clicking the Continue
icon on the toolbar or Debug → Continue on the top menu bar. The program will now run
until variable var has been incremented to 10, after which the breakpoint will stop the
program and hand control over to the debugging software.
14.5.9 • Stepping into a Function
We have been stepping over lines of code which will step over any functions and not
step into them and show execution of lines of code in the function. Single-step until the
cursor is over the Delay() function and then click the Step Into icon on the toolbar or
click Debug → Step Into to step into the function. Alternatively press the F11 key on the
keyboard. The cursor now moves into the function and we can single-step through lines of
code in the function.
Add the del variable to the watch window by right-clicking it and selecting Add Watch
from the pop-up menu. Step through the code in the while loop in the function to see
the del variable decrementing in the watch window. The del variable is initialised to a
very large value and it would take a long time to decrement it to zero in order to exit the
function. Click the Step Out button on the toolbar, or click Debug → Step Out or press
Shift + F11 on the keyboard to step out of the function and back into main().
When code execution continues in main() after returning from the delay function, the
local variable del is destroyed and no longer exists. The watch window now shows
"Unknown identifier" next to del because it no longer exists. If we were to step back into
the delay function, the variable would be created again and initialised and we would be
able to view it in the watch window again.
14.6 • Enabling ISP on the AVR / Disabling debugWIRE
To enable the default ISP programming on the AVR, debugWIRE must be disabled. While
the debugger is still running, click Debug → Disable debugWire and Close from the top
menu. If the debugging session is closed, this option will not be enabled on the menu,
so the debugger must be started again before disabling debugWIRE. This can be done by
clicking the Start Debugging and Break button on the toolbar or on the top Debug menu
bar.
After changing back into ISP mode, ISP must be selected as the connection method to the
AVR in the tool settings. Click the button on the toolbar that displays debugWIRE on AVR
Dragon to open the tool settings page, select ISP as the interface and save the changes.
14.7 • Exercises
These exercises are just things to try with the debugging tools and do not have any
answers.
•
Put a breakpoint in the body of the if that checks if the switch is closed in the
while(1) loop of the program from this chapter. When the switch is pressed
program execution will be handed over to the debugger.
•
Enable compiler optimisation and try using the debugger.
● 277
C Programming with Arduino
14.8 • Summary
● 278
•
Debugging tools can be used to help with debugging a program and to examine
the contents of variables and memory.
•
Watches can be set in the debugger to display the contents of selected
variables.
•
Breakpoints set in a program will stop the program and hand control over to
the debugger software when program execution hits a breakpoint.
•
Lines of code can be single-stepped through using the debugger and functions
can be stepped into and out of.
•
AVR microcontrollers on the Arduino Uno and MEGA boards can be debugged
by using the on-chip debugWIRE system.
•
In order to use debugWIRE the DWEN (debugWIRE enable) fuse must be set.
Chapter 15 • Interrupts
Chapter 15 • Interrupts
What you will do in this chapter:
•
Find out how interrupts work
•
Use interrupts for servicing hardware instead of using polling
•
Use timer interrupts
Up until now we have been "polling" hardware – continually checking it in our program
to see if some condition has occurred. If you look at the timer program in section 13.1.2
on page 255, you will see one of the disadvantages of polling hardware. When the
TC1Delay() function is called, it uses up all of the microcontroller’s processing time to
check or poll the timer. If this function was to be used in a more useful program, the rest
of the embedded system would not be able to respond to user button presses or other
hardware that may need to be serviced while this function was running. Interrupts on a
microcontroller are available to solve this problem.
15.1 • Handling Interrupts in C
There is no standard way of handling interrupts in C. Interrupt configuration depends on
on how interrupts have been implemented in the architecture of a specific microcontroller
and how interrupts have been implemented in the compiler. Different C compilers for the
same microcontroller may use different code to configure interrupts, so always check the
compiler documentation to see how to use interrupts.
Interrupts can be configured on the microcontroller so that, for example, when using
the timer, it can be started and left to run on its own while the main program continues
to run. When the timer reaches the time-out value e.g. after 1ms, an interrupt will be
triggered. This interrupts the current flow of the main program and a function called an
Interrupt Service Routine (ISR) is run in order to service the timer. When the interrupt
occurs, the current state of the microcontroller is saved, a jump occurs to the ISR
function where code is run that will respond to the timer. When the ISR function has
finished running, a jump occurs back to where the main program left off and the saved
state of the microcontroller is restored. The program carries on running normally after
this until another interrupt occurs.
15.2 • Using the Timer Interrupt
The timer_interrupt program uses 16-bit timer/counter 1 to generate an interrupt every
second. When the timer time-out is reached after 1s, the interrupt service routine is run
which increments a count value representing seconds elapsed.
In the while(1) loop of the program an if statement checks if the count value has
changed. Only if the count value has changed will the new count value be sent to the
terminal window so as not to flood the terminal with count values which would happen if
the count value was continually sent in the main loop.
Create the timer_interrupt program using the template file.
● 279
C Programming with Arduino
Project name: timer_interrupt
Project location: CH15\timer_interrupt
main.c
#include
#include
#include
#include
<stdio.h>
<avr/io.h>
<avr/interrupt.h>
"stdio_setup.h"
volatile unsigned long g_count_s = 0;
int main(void)
{
unsigned long saved_count = 0;
OCR1A = 15624;
TCCR1B = (1 << WGM12) | (1 << CS12) | (1 << CS10);
TIMSK1 = (1 << OCIE1A);
sei();
UartInit();
}
while(1) {
if (saved_count != g_count_s) {
printf("%lu\r\n", g_count_s);
saved_count = g_count_s;
}
}
ISR(TIMER1_COMPA_vect)
{
g_count_s++;
}
Sample Program Output
1
2
3
4
...
In the above code the timer is set up and started as before, but this time it is set to
trigger an interrupt every second. When the timer times out and triggers the interrupt,
the code that is running in the while(1) loop is "interrupted" and the
ISR(TIMER1_COMPA_vect) interrupt service routine is run. This ISR function simply
increments the global variable g_count_s by one. When the ISR function returns, the
code in the while(1) loop continues running where it left off when the interrupt occurred.
Let’s have a look at how the code works.
In the following two lines of code timer/counter 1 is set up with a 1 second period,
prescaler division of 1024 and CTC mode as done in the polled timer example.
OCR1A = 15624;
TCCR1B = (1 << WGM12) | (1 << CS12) | (1 << CS10);
● 280
Chapter 15 • Interrupts
Bit OCIE1A (output compare A match interrupt enable) in TIMSK1 (interrupt mask
register) is set in order to enable an interrupt when the count value of the timer reaches
the value in OCR1A which occurs when one second has elapsed in this code.
TIMSK1 = (1 << OCIE1A);
The sei() macro sets the I flag in the AVR’s status register which globally enables
interrupts. Setting the I flag is necessary to enable all interrupts for hardware devices
such as the timer or other peripherals that have been enabled using their interrupt enable
bits in their interrupt registers. The cli() macro can be used to clear the I bit in the
status register which globally disables all enabled interrupts.
sei();
In the following code that runs continually in the while(1) loop, a saved copy of the
global count variable g_count_s is compared with the global count variable. g_count_s is
independently incremented by the interrupt service routine every second. When
g_count_s is incremented, it differs from the saved version of this count value in
saved_count and the if statement evaluates to true. When the if statement evaluates
to true then the new count value is printed to the terminal window and saved in the
saved_count variable. This results in the count value only being printed to the terminal
window when it changes, which occurs every second.
if (saved_count != g_count_s) {
printf("%lu\r\n", g_count_s);
saved_count = g_count_s;
}
The AVR GCC library makes the ISR() macro available that marks the interrupt service
routine function. The name of the interrupt service routine must be one of the pre-defined
ISR names and is TIMER1_COMPA_vect for the timer 1 compare A interrupt in this
example. In other words you may not give an ISR a name of your own choice.
ISR(TIMER1_COMPA_vect)
{
g_count_s++;
}
In this program the ISR simply increments the global count variable g_count_s by one
when the ISR is run every second, which occurs when the timer count value reaches the
value set in OCR1A.
Writing Interrupt Service Routines (ISRs)
Always do as little as possible in an interrupt service routine so as not to hold up other
code from running in the main loop and to prevent blocking the next interrupt or other
interrupts, should they be enabled. As an example if you are using the USART interrupt to
receive data, get the data byte in the ISR and save it to a buffer, then set a flag so that
the main software knows that data is available.
15.3 • One Millisecond Tick Interrupt
A one millisecond tick interrupt is very useful in an embedded system. It can be used to
generate different time delays that are multiples of 1ms and be used to create the time
● 281
C Programming with Arduino
period for debouncing a switch amongst other applications. Create the int_1_ms project
using the template file.
Project name: int_1_ms
Project location: CH15\int_1_ms
main.c
#include
#include
#include
#include
#include
<avr/io.h>
<avr/interrupt.h>
<util/atomic.h>
<stdio.h>
"stdio_setup.h"
// interval in milliseconds to print time
#define TIME_PERIOD_MS 1000
// milliseconds elapsed since timer started
volatile unsigned long g_time_ms = 0;
int main(void)
{
unsigned long prev_time_ms = 0;
// saved elapsed time
UartInit();
// set
OCR0A
TCCR0A
TCCR0B
TIMSK0
sei();
}
up counter/timer 0 and enable interrupt
// count value for 1ms period
= 249;
// CTC mode
= (1 << WGM01);
// divide by 64 prescaler
= (1 << CS01) | (1 << CS00);
// enable interrupt
= (1 << OCIE0A);
// global interrupt enable
while(1) {
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
// check if desired time period has elapsed
if ((g_time_ms - prev_time_ms) >= TIME_PERIOD_MS) {
// save the current time
prev_time_ms = g_time_ms;
// print elapsed ms
printf("%lu\r\n", g_time_ms);
}
}
}
// 1ms tick interrupt
ISR(TIMER0_COMPA_vect)
{
g_time_ms++;
}
Sample Program Output
1000
2000
3000
4000
5000
…
● 282
Chapter 15 • Interrupts
Sample Program Output without ATOMIC_BLOCK
1000
2000
3000
4000
4864
5864
6864
7864
8864
9728
10728
…
The int_1_ms project uses timer/counter 0 in CTC mode to generate an interrupt every
millisecond which is our 1ms tick interrupt. In the ISR of the timer a global variable called
g_time_ms is incremented which stores the number of milliseconds elapsed since the
timer was started. Code in the while(1) loop uses the elapsed millisecond value to create
a one second time period in which the elapsed milliseconds are printed to the terminal
window.
Timer/counter 0 is set up to use a prescaler value of 64 which then feeds a clock pulse
with a 4µs period to the timer when the main 16MHz clock is divided by 64. To find
the count value that the timer must count to for a 1ms period, we divide 1ms by 4µs
which gives 250. One must be subtracted from the calculated value of 250 as previously
explained, which gives a value of 249 that the compare register A (OCR0A) must be
initialised to.
After the timer has been set up and interrupts enabled, the code in the while(1) loop
runs with the 1ms timer interrupt interrupting it every millisecond to increment
g_time_ms. The if statement is put into a special block that will be explained shortly.
The expression in the if statement will evaluate to true when the time period defined
with TIME_PERIOD_MS has elapsed. Subtracting a saved copy of the elapsed millisecond
time from the current time calculates the time that has elapsed since the last time that
the if expression evaluated to true. When the if expression does evaluate to true, a copy
of the current elapsed time from g_time_ms is saved to prev_time_ms and will be used in
the following if comparisons. A printf statement then prints out the current millisecond
time.
It is necessary to use the ATOMIC_BLOCK() macro that is defined in header file atomic.h,
and to insert accesses to g_time_ms into the body of this block in order to prevent this
variable from being corrupted. If an interrupt occurs when the global variable g_time_ms
is being read, then this variable could be incremented by the code in the ISR during the
four byte read of this variable that occurs in the main code. Because the AVR is an 8-bit
microcontroller it can only access a byte at a time, so C code that reads a four byte wide
variable results in four byte wide reads in the machine code generated by the compiler.
Atomic, in this context, means that the code in the atomic block can not be interrupted.
Code placed in the body of ATOMIC_BLOCK is guaranteed to never be interrupted. Passing
a type of ATOMIC_RESTORESTATE to the ATOMIC_BLOCK macro restores the SREG status
register after execution of the atomic block completes.
Sample program output for the above code without the atomic block is shown above
demonstrating that the value of the global variable gets corrupted.
● 283
C Programming with Arduino
15.4 • Summary
● 284
•
The C language does not have a standard way of handling interrupts.
•
Interrupts are implemented by the writers of a C compiler, so it is necessary to
read the compiler documentation for each different compiler to find out how to
use interrupts in a C program.
•
Interrupts for AVR peripherals are enabled by setting an interrupt bit in one of
the registers of each peripheral device.
•
In order for all enabled interrupts to work, global interrupts must be enabled by
using the sei() macro.
•
Code that must run without being interrupted can be put into an atomic block.
Chapter 16 • Wrapping Up the C Language
Chapter 16 • Wrapping Up the C Language
What you will do in this chapter:
•
Use structures to group different data types together
•
Use pointers to access structures
•
Learn about unions
•
See how the enumerated data type works
•
Look at miscellaneous topics in C to complete coverage of the C language
16.1 • Structures
Arrays allow us to store and access more than one variable of the same type. Structures
can be used to group data of different types, for example a structure can contain a float,
int and a char, but an array can only consist of one of these types.
16.1.1 • Using Structures in C Programs
The sample_struct project uses a structure to group a time-stamp value and sample
data value together in order to demonstrate the use of structures in C. Create this project
using the template file.
Project name: sample_struct
Project location: CH16\sample_struct
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
struct sample {
int time;
float data;
};
struct sample samp1;
/* declare a data type structure */
/* integer member of structure
*/
/* float member of structure
*/
/* define a structure of type sample */
UartInit();
}
/* assign values to struct members
samp1.time = 20;
samp1.data = 56.23;
printf("\r\nTime in seconds is: %d\r\n", samp1.time);
printf("Sampled data is: %.2f", samp1.data);
while (1);
*/
Program Output
Time in seconds is: 20
Sampled data is: 56.23
● 285
C Programming with Arduino
A structure can contain any number of different data types and the compiler therefore
does not know what you are going to put in a structure, so the structure must first be
declared. In the sample_struct program, this is done with the following statement:
struct sample {
int time;
float data;
};
This declares a new data type called struct sample which has two member variables.
The name sample is known as a tag and is a type name – i.e. we have declared a new
data type that can now be used in our program. Figure 16-1 shows the details of a C
structure declaration.
Figure 16-1 A C Structure Declaration
In the same way that we can use the keyword int to define an integer variable in a
program, we can use the new data type struct sample to define a new structure of this
type. This is what the next statement in the program does:
struct sample samp1;
The declaration of the new structure type did not allocate any memory for the structure,
it just tells the compiler of the new data type that we are going to use in our program.
The above statement now creates a new variable called samp1 of type struct sample.
This allocates memory for the structure and the variable name for this structure is now
samp1. We could declare another variable in the program of type struct sample as follows:
struct sample samp2;
This will define another structure in the program of type struct sample with its own
name. In other words a second structure of the same type would exist in memory that
can be accessed by using its name – samp2, just like we could define two integers or
other variable types in a program:
int val1;
int val2;
struct sample samp1;
struct sample samp2;
Each member variable of the structure can be accessed with the dot (.) operator as we
can see in the following lines from the program:
samp1.time = 20;
samp1.data = 56.23;
● 286
Chapter 16 • Wrapping Up the C Language
In each case, the structure name followed by the dot operator and then the name of the
member variable is used to access the member variable of the structure.
16.1.2 • Initialising Structures
Structures, like arrays can be initialised with data values. The init_struct program
initialises three structures that each contain a pattern to display on the LEDs and the
duration to display it for. Create this project without the use of the template file. Connect
LEDs to port C pins PC0 to PC3 as done in previous projects and shown in appendix C.
Project name: init_struct
Project location: CH16\init_struct
main.c
#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
int main(void)
{
struct pattern_time {
unsigned char pattern;
unsigned int time_ms;
};
struct pattern_time pt1 = {0x05, 2000};
struct pattern_time pt2 = {0x0A, 5000};
struct pattern_time pt3 = {0x03, 3000};
DDRC |= 0x0F;
}
while(1) {
PORTC = pt1.pattern;
_delay_ms(pt1.time_ms);
PORTC = pt2.pattern;
_delay_ms(pt2.time_ms);
PORTC = pt3.pattern;
_delay_ms(pt3.time_ms);
}
Program Output
The following bit patters are displayed on the four LEDs in a continuous loop:
0101 for 2 seconds
1010 for 5 seconds
0011 for 3 seconds
A structure is declared that contains two member variables that are used to store a bit
pattern to be displayed on the LEDs, as well as the duration to display the pattern for in
milliseconds.
Three structures are then defined and initialised using the structure that was declared.
This makes space in memory for three structures named pt1 to pt3 and assigns values
to their member variables. In the structure, the pattern member variable occurs first
and the time_ms member variable occurs second. When initialisation takes place, the first
value in the braces is assigned to the first member variable – pattern and the second
value in the braces is assigned to the second member variable – time_ms.
● 287
C Programming with Arduino
16.1.3 • Pointers to Structures
The next example, point_struct, uses a pointer to a structure in order to show the syntax
used to access structure members with a pointer. Create this project from the template
file.
Project name: point_struct
Project location: CH16\point_struct
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
struct time {
unsigned char hour;
unsigned char minute;
unsigned char second;
};
struct time cur_time = {12, 34, 27};
struct time *t_ptr;
t_ptr = &cur_time;
/* structure variable */
/* pointer to struct */
UartInit();
printf("Time is %02d:%02d:%02d\r\n",
t_ptr->hour, t_ptr->minute, t_ptr->second);
}
while(1);
Program Output
Time is 12:34:27
This program first declares a structure of type struct time. A structure of type struct
time called cur_time is then defined and initialised. A pointer to a structure of type
struct time is then defined called t_ptr. This makes space in memory to store the
address of a structure of this particular type.
The line t_ptr = &cur_time; assigns the address of the cur_time structure to the
structure pointer t_ptr. When the contents of the structure is accessed by the pointer in
the printf() function, the arrow operator (->) is used to access each member variable.
The arrow operator is always used to access member variables of a structure when we
are using a pointer to a structure. The dot operator is always used to access the member
variables of a structure when we are not using a pointer to a structure, but are accessing
the structure directly.
16.2 • Unions
A union in C uses the same syntax as a structure, except that the keyword union is used
instead of struct when declaring it. The members of a union share the same space in
memory which allows us to access the single space in memory as one type of variable or
another.
● 288
Chapter 16 • Wrapping Up the C Language
The union program demonstrates how a C union works. Create this project using the
template file.
Project name: union
Project location: CH16\union
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
union unnum {
float flt;
long dec;
};
union unnum my_union;
UartInit();
my_union.flt = 23.7;
printf("\r\nnum as float is %.2f\r\n", my_union.flt);
printf("num as long int is %ld\r\n", my_union.dec);
my_union.dec = 100;
printf("\r\nnum as float is %.2f\r\n", my_union.flt);
printf("num as long int is %ld\r\n", my_union.dec);
}
while (1);
Program Output
num as float is 23.70
num as long int is 1102944666
num as float is 0.00
num as long it is 100
A union called unnum is declared in this program. A union called my_union is then defined.
The union contains a floating point member variable as well as an integer member
variable. The integer is of type long int to make it the same number of bytes wide as a
float on an AVR – both of these variables occupy four bytes of memory. When they occur
in a union, they both occupy the same four bytes of memory, so the same four bytes of
memory can be viewed as an integer or a floating point number.
In the program the floating point member variable is first initialised with a value of
23.7. This value is then displayed in the terminal window as a floating point number
and then as an integer. To display the number as a floating point number, we access the
floating point member variable flt. To display this number (4 byte memory location)
as an integer, we access the integer member variable dec. This results in a value of
1102944666 being displayed because the two numbers are in different formats i.e. the
floating point number contains an integer part and a fractional part in the four byte
memory location and this gives us a meaningless number when we view these four bytes
as a decimal number.
Take note that viewing the same memory location as a float and int is completely
different to viewing a variable as a float and then casting it to an int or copying a float
● 289
C Programming with Arduino
to an int and truncating the fractional part of the float. Looking at the same memory
locations as a floating point number and then integer reveals the underlying encoding
of the floating point number. To prove that we are looking at the same four bytes in
memory, the integer member variable is initialised to a value of 100. This causes the
floating point member variable to change to 0.00 when printed in the terminal window.
A more practical use of a union is demonstrated in the next program called multibyte
where a union is used to access an integer as a 32-bit integer or as individual bytes. This
could be used in a program to access a 32-bit number as four single bytes to send over a
serial port that sends a byte at a time. Create this project using the template file.
Project name: multibyte
Project location: CH16\multibyte
main.c
#include <stdio.h>
#include "stdio_setup.h"
int main(void)
{
int count;
union wrd_byte{
unsigned long word;
unsigned char byte[4];
} port;
UartInit();
}
port.word = 0xFF5501AA;
printf("\r\nWord is: 0x%08lX\r\n", port.word);
for (count = 0; count < 4; count++) {
printf("Byte%d: 0x%02X\r\n", count, port.byte[count]);
}
while (1);
Program Output
Word is: 0xFF5501AA
Byte0: 0xAA
Byte1: 0x01
Byte2: 0x55
Byte3: 0xFF
In this program a union is declared and a union with the name port is defined in one
step by putting the name of the union to be defined between the closing brace and
the terminating semicolon of the union declaration. The union consists of two member
variables: a four byte integer called word and a four byte array called byte. In the
program the word member variable is initialised to a value of 0xFF5501AA and then
printed in the terminal window. A for loop is used to print the same number as four
individual bytes by accessing each byte of the byte array. Of course because the byte
and word member variables share the same four bytes in memory, the value that
word was initialised to is displayed in the terminal in four parts starting with the least
significant byte in word. Figure 16-2 on page 291 shows what the union looks like in
memory. The figure shows that the word variable and byte array take up the same four
memory locations.
● 290
Chapter 16 • Wrapping Up the C Language
Figure 16-2 A union in Memory
16.3 • Enumerated Type
The enumerated data type in C is an integer data type that can be used to make a
program easier to read and understand. The enumerated data type allows you to create
your own data type and specify the values that can be assigned to it. If an attempt is
made to assign a value other than you have specified, the compiler will generate an error.
The next program uses the Arduino as a controller for an imaginary dough mixing
machine. Three LEDs on port pins PC0 to PC2 represent three solenoids that are used to
control the machine. The switch on pin PC5 is used by the machine operator to start the
machine. The hardware is configured as follows:
Outputs:
PC0 = water pump solenoid – adds water to the mixing bowl when activated
PC1 = flour hopper solenoid – adds flour to the mixing bowl when activated
PC2 = mixer – starts and runs the mixer when activated
Input:
PC5 = start button – pressed by the machine operator to make a batch of dough
Run the program and press the button on PC5 to start the dough mixing procedure. Each
of the LEDs (solenoids) will switch on in sequence so that water is added to the bowl for 4
seconds, flour is added for 2 seconds and the dough is mixed for 5 seconds. After this the
machine goes back into standby mode and waits for the operator to press the start button
again to make another batch of dough. The enum_seq program is used to demonstrate
enumerated data types in C. Create this project without the template file.
Project name: enum_seq
Project location: CH16\enum_seq
main.c
#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
int main(void)
{
enum machine_state { standby, pump_water, add_flour, mixing };
enum machine_state state = standby;
● 291
C Programming with Arduino
DDRC = 0x07;
/* LEDs on PC0, PC1 and PC2 */
while (1) {
switch (state) {
case standby:
/* switch all solenoids off */
PORTC &= 0xF8;
/* check for start button pressed */
if (PINC & (1 << PINC5)) {
state = pump_water;
}
break;
case pump_water:
/* switch water pump solenoid on for 4s (PC0) */
PORTC = (1 << PORTC0);
_delay_ms(4000);
state = add_flour;
break;
case add_flour:
/* switch flour hopper solenoid on for 2s (PC1) */
PORTC = (1 << PORTC1);
_delay_ms(2000);
state = mixing;
break;
case mixing:
/* mix dough for 5s (PC2) */
PORTC = (1 << PORTC2);
_delay_ms(5000);
state = standby;
break;
}
}
}
default:
break;
Program Output
When the push-button switch on PC5 is closed, the following happens:
- The LED on PC0 switches on for four seconds
- The LED on PC0 switches off and the LED on PC1 switches on for two seconds
- The LED on PC1 switches off and the LED on PC2 switches on for five seconds
- The LED on PC2 switches off and the push-button switch is monitored again
Inside main() an enumerated data type is declared as shown below.
enum machine_state { standby, pump_water, add_flour, mixing };
The enum keyword tells the compiler that this is to be an enumerated data type. The tag
machine_state gives it a name so that we can use it in our program. The four names
between braces are the four possible values that an enumerated data type of this kind
can take on. These names are actually integer values that start from 0 and count up,
so standby has the value 0, pump_water has the value 1, add_flour has the value 2,
etc. These are not variable names, but are actual integers that we refer to by these
meaningful names.
● 292
Chapter 16 • Wrapping Up the C Language
The next line in the program creates a variable of type enum machine_state called state
and assigns it the value of standby.
enum machine_state state = standby;
We now have a variable called state that can take on only one of the values that are part
of the enumerated type i.e. standby, pump_water etc.
DDRC is used to set up PC0, PC1 and PC2 as outputs for our three imaginary solenoids
that are simulated on LEDs attached to these pins by setting the lower three bits of this
register.
A switch statement is used to sequence the machine. The switch statement used in
this way is known as a "state machine" as it controls the state of the program. When the
switch statement is run, it checks the value of the enumerated variable state. Because
we initialised this variable with the value of standby, the first case statement will be
continually run as it is inside the while(1) loop. This case statement is used to check if
the start button is pressed by the machine operator. It also switches all of the solenoid
valves (LEDs) off. Should the operator press the start button that is wired to pin PC5, the
value of the enumerated data type state is changed to the value of pump_water inside
the if statement. This results in the next case statement (pump_water) being run the
next time that the switch statement is executed.
The pump_water case switches on the water pump solenoid for four seconds and then
changes the state of the state machine to add_flour. The add_flour and mixing states
operate in a similar manner, each switching off the previous solenoid valve and switching
on a new one, then waiting for a specified time period before changing the state of the
state machine. When the mixing state has been completed, the state is changed back to
standby and the push button switch is again monitored for the operator to start a new
batch of dough.
Note how the enumerated data type makes the program easier to understand as it
uses easy to understand names instead of numbers to represent the four states of the
machine.
Also notice that bitwise assignment operators were not used to switch the LEDs on,
instead a bit-shifted value is written directly to the PORTC register which then switches
all other LEDs off and only the selected LED on. This method can be used when it makes
sense to do so and has the advantage that the previously switched on LED does not need
to be switched off first in a separate line of code.
Switch debouncing is not needed and is automatically built into the program because of
the way the program is written. When the switch is closed, it is not monitored again until
the entire LED switching sequence is complete, so the microcontroller will never read
more than one switch press before moving to the next step in the sequence. Calls to the
delay function in the program effectively give the switch many seconds of time for the
switch contacts to stop bouncing when the push-button switch is closed and again when it
is opened.
16.4 • The typedef Declarator
The typedef keyword enables us to give another name to a C data type. This can be used
to make a program easier to read and write. The following lines of code show how to use
typedef to give a different name to a C data type:
typedef unsigned char BYTE;
typedef unsigned char UCHAR;
● 293
C Programming with Arduino
Either BYTE or UCHAR can now be used in a program wherever we would have used
unsigned char – this has effectively made a new C data type. Using the name BYTE is
more descriptive especially when programming hardware. UCHAR is a shorter way of
writing unsigned char and can save some typing and space on a line especially when a
function takes a number of arguments that are of this type. In the program we can now
define variables of this type:
BYTE port1_val;
BYTE port2_val;
And is that same as:
unsigned char port1_val;
unsigned char port2_val;
typedef can also be very useful when used to create a new structure. The typedef
declaration can be made in a header file and every source file that the header file is
included in can make use of the new data type. As an example:
typedef struct {
unsigned char hours;
unsigned char minutes;
unsigned char seconds;
} time_struct;
Notice that we do not even need a tag name in this typedef declaration. This structure
can now easily be used in any program as follows:
time_struct curr_time = { 2, 32, 56 };
time_struct alarm_time = { 3, 30, 00 };
We have effectively created a new data type called time_struct and can now create
variables of this type by using its type name.
16.5 • Storage Class Specifiers
There are four storage class specifiers in C of which you have already seen the static
specifier. The other three are auto, register and extern. Only one storage class
specifier may appear in a declaration. The meaning of each of these specifiers is
described below:
auto – This keyword is actually outdated as any variable that is declared in a function
is an automatic variable. When a variable is declared with the auto specifier it will have
automatic storage duration. This means that when a function is called, the automatic
variable will be created (it will be given space in memory) and when the function exits,
the variable will be destroyed (the memory will be freed for use by other parts of the
program). This happens by default anyway. Example of usage:
auto int val;
extern – Functions and variables declared with the extern keyword can be used
anywhere in the program. They will have "global" visibility. e.g:
extern int val;
register – This keyword is a suggestion to the compiler that you want to store a variable
in an internal CPU register to make access to it faster. This is not however guaranteed to
happen. You can not use the address operator with variables that are declared with this
● 294
Chapter 16 • Wrapping Up the C Language
specifier. e.g:
register float fast_flt;
static – Variables that are declared static will retain their value even when they go out
of scope e.g. when a function in which they exist returns, they will not be destroyed. e.g:
static unsigned int count;
An example of the use of the extern keyword is if our C program consists of more than
one file and we want to be able to use the global variable that was created in one file in
the second file, we would use the extern keyword in the second file. For example:
In main.c we create a global variable (a variable defined outside of a function):
int globvar = 2;
We now want to use this global variable in another file in this program – comms.c. In
comms.c we add the following at the top of the file or in a header file that is included in
comms.c:
extern int globvar;
We are now free to use globvar in the comms.c file.
16.6 • Type Qualifiers
C has two type qualifiers namely const and volatile.
const – a variable that is qualified with the const keyword is constant and it can not be
changed by the program. A variable qualified with const is effectively "read only" and
code that tries to write to this variable will be flagged as an error by the compiler.
Example of using const:
const int ref = 3;
volatile – we have already used the volatile keyword in several example programs.
This keyword forces the program to reread the value that is qualified as volatile
whenever it is used as it may be modified by an external process or event.
An example of using volatile is on a variable that is modified by an interrupt service
routine, but never modified by the main program. If compiler optimisation is enabled,
which is the default in an Atmel Studio project, the compiler may optimise the variable
out of existence if it is not qualified by the volatile keyword.
16.7 • The goto Statement
Use of the goto statement is considered bad programming practice by most C
programmers. The goto statement can make programs hard to understand and debug
because it can cause program flow to jump anywhere in a program. Using the goto
statement is unnecessary in a program and there will always be a better way to write a
program without this statement by using a loop or if statement. How to use the goto
statement is included here as you may come across it in someone else’s code and will
need to understand how it works.
● 295
C Programming with Arduino
The goto statement is always used in conjunction with a label. An example will illustrate:
if (time == alarm_time)
goto ring_alarm;
else
CountSeconds();
ring_alarm:
AlarmOn();
TimeDelay(5000);
AlarmOff();
The goto statement will cause a jump to the ring_alarm label and the code under this
label will be run.
16.8 • A List of All C Keywords
Table 16-1 contains a list of all 32 C keywords as a summary of what we have covered.
Table 16-1 All C Keywords
auto
double
int
struct
break
else
long
switch
case
enum
register
typedef
char
extern
return
union
const
float
short
unsigned
continue
for
signed
void
default
goto
sizeof
volatile
do
if
static
while
16.9 • More Preprocessor Directives
We have used the #include and #define preprocessor directives in various example
programs. There are other preprocessor directives that can be used for conditional
compilation. The #ifdef directive can be used to compile certain parts of a program and
not others when used in combination with the #define and #endif directives.
An example of the use of conditional compilation is if we want to write a C program that
can run on two different microcontrollers or on two different embedded systems that
may have slightly different hardware. We would want to compile the same program to
use one microcontroller or embedded system’s hardware, but would also want to be able
to compile the same program to use on the other microcontroller or embedded system
hardware. Preprocessor directives can be used to add in and remove code depending
on which microcontroller or embedded system is selected. The following program called
conditional_compile demonstrates how to compile conditionally for the Arduino Uno and
MEGA that have the on-board LED attached to different port pins.
● 296
Chapter 16 • Wrapping Up the C Language
Project name: conditional_compile
Project location: CH16\conditional_compile
main.c
#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
#define ARDUINO_UNO
int main(void)
{
#ifdef ARDUINO_UNO
DDRB = 0x20;
#else
DDRB = 0x80;
#endif
}
while(1) {
#ifdef ARDUINO_UNO
PORTB ^= 0x20;
#else
PORTB ^= 0x80;
#endif
_delay_ms(1000);
}
Program Output
The on-board LED flashes on and off.
This program can be compiled for the Arduino Uno or the Arduino MEGA and will flash
the on-board LED on and off despite the on-board LED being attached to different port
pins on each board. When compiling for the Uno, ARDUINO_UNO must be defined as it
is shown in the code listing above. When compiling for the MEGA, the line of code that
defines ARDUINO_UNO must either be commented out or could be changed to ARDUINO_
MEGA.
When the program is compiled with ARDUINO_UNO defined as above, the code under
the #ifdef ARDUINO_UNO directives will be included in the program by the pre-processor
and the code between the #else and #endif directives will be excluded. When #define
ARDUINO_UNO is removed from the file, commented out or the name changed, only the
code between the #else and #endif directives will be compiled. The #endif directive is
used to show that the #ifdef and #else directives end here – like the closing brace of a
construct in C.
These preprocessor directives conditionally compile the lines of code that need to be
different for handling the hardware differences between the two boards. At the beginning
of the program the port pin that the LED is attached to is set as an output pin by
conditionally selecting the correct line of code to set the correct bit in DDRB. The same
happens when the LED is toggled – the correct bit in PORTB is toggled for the selected
hardware.
● 297
C Programming with Arduino
16.10 • C99 Variable Types
Most technical standards are updated every few years to add improvements to the
standards and to include changes that need to be made due to improving or changing
technology or usage of the standards. The C language standard is no exception and the
C99 revision of the language added new data types, some of which are shown below.
Fixed width integers:
int8_t
– equivalent to signed char in the AVR GCC compiler
int16_t
– equivalent to short and int in the AVR GCC compiler
int32_t
– equivalent to long in the AVR GCC compiler
Fixed width unsigned integers:
uint8_t
– equivalent to char and unsigned char in the AVR GCC compiler
uint16_t
– equivalent to unsigned short and unsigned int in the AVR GCC compiler
uint32_t
– equivalent to unsigned long in the AVR GCC compiler
These fixed width integer types are found in the stdint.h header file that must be included
in the C source files that use these data types. The number in each of these data types
is the bit width of the integer – uint8_t is an 8-bit unsigned integer, int32_t is a signed
32-bit integer, etc.
Fixed width data types solve the problem with varying integer widths that occurs when
porting C code between different microcontrollers and/or compilers. When an integer is
defined with the int keyword, it could be 16-bits wide, 32-bits wide, or more, depending
on the microcontroller and compiler being used. Fixed width integer data types solve
this problem by specifying the width of the integer and will be the same width no matter
which microcontroller or compiler are used. An unsigned 16-bit wide integer defined with
uint16_t will be 16-bits wide whether it is defined in code that is compiled for an 8-bit
AVR or a 32-bit ARM microcontroller.
16.11 • Alternative Continuous Loop
There is an alternative way of writing a continuous loop like we have been doing with
while(1), but using a for loop instead. You may come across this way of writing a
continuous loop when looking at C code from other authors. The following two code
listings do exactly the same thing, creating a continuous loop that runs the code placed in
the body of the loop forever.
while(1) {
}
for(;;) {
}
● 298
Chapter 16 • Wrapping Up the C Language
16.12 • Summary
•
Structures can be used to group data of different types together.
•
The dot operator is used to access members of a structure.
•
The arrow operator is used to access structure members when using a pointer
to the structure.
•
A union is similar to a structure but its member variables share the same
memory space.
•
The enumerated data type can be used to make a program easier to read and
understand.
•
The typedef declarator can be used to rename a data type or give a name to a
new data type such as a user defined structure.
•
Storage class specifiers (auto, extern, static, register) can be used to modify
the way a program treats a variable.
•
The const keyword specifies that a variable is constant and cannot be changed
in the program.
•
The volatile keyword is used wherever a register or variable can be changed
by hardware or a process that operates externally to the C program and can
change the value of the register or variable without the program knowing about
the change.
•
The goto statement can make a program difficult to understand as it can
cause a jump to anywhere in the program. It is considered good programming
practice not to use the goto statement as there is always a better way of
writing a program without this statement.
•
C code can be conditionally compiled by using preprocessor directives.
•
Fixed width integer data types from the C99 revision of the C language allow
the width of integers to be specified.
● 299
C Programming with Arduino
● 300
Chapter 17 • C Projects
Chapter 17 • C Projects
What you will do in this chapter:
•
Write serial port driver functions to replace printf()
•
Write functions to convert numbers to decimal and hexadecimal strings
•
Make a voltmeter project
•
Create a stopwatch
This chapter consists of a series of C code projects that demonstrate the use of the C
language already learned in the previous chapters. Several C topics are added to and
expanded in the explanations that follow each project.
17.1 • Serial Port Driver Functions
As already discussed, the printf() functions tends to use up a fair amount of memory
which is a scarce resource on 8-bit AVR microcontrollers, especially the smaller parts
that have less memory. The solutions suggested in Chapter 10 was to write serial port
functions that can send and receive text and characters over the UART serial port. The
project in this section modifies the code from the serial_port project found in section
10.5.2 of Chapter 10 on page 184. The following modifications are made to the serial_
port project.
•
The serial port / UART driver functions are put into their own C file
•
UART baud rate settings and microcontroller clock frequency are set in global.h
•
An extra function is added that transmits a null-terminated string
•
The use of commenting is demonstrated in the program
The serial port driver functions allow strings to be sent over the Arduino serial / USB
port in the same way as the printf() function does when a project is created using the
template file, but the serial port driver functions do not have the formatting capabilities
of printf(). Using the serial port function for transmitting a string instead of printf()
reduces program memory usage.
17.1.1 • Serial Port Driver Design Notes
Although the hardware device in the AVR microcontroller that handles serial port
communications is called a USART, it is used as a UART in the serial port driver code so
file names and function names to do with using the USART are named UART and not
USART.
The uart_driver project that contains the serial port driver functions is split into four
separate source code files, namely main.c, uart.c, uart.h and global.h. main.c contains
the code that demonstrates the use of the serial port functions from uart.c. uart.h is the
header file for uart.c and must be included in any C source file that uses the serial port
driver functions. global.h contains the clock frequency of the embedded system that the
code will be compiled for, defined as F_CPU, as well as the baud rate setting for the UART.
It was decided to put the baud rate setting into the global header file so that the baud
rate can be changed without making any changes to the UART driver
● 301
C Programming with Arduino
files. In this way the driver files can be copied from project to project without any need to
modify them. Any changes to the baud rate or microcontroller frequency can be made in
the global.h header file.
17.1.2 • Serial Port Driver Code
Code listings for all four source code files of the uart_driver project are shown below.
Project name: uart_driver
Project location: CH17\uart_driver
main.c
/*-----------------------------------------------------------------------------FILE NAME:
main.c from uart_driver project
DESCRIPTION:
Tests the UART driver functions from uart.c
AUTHOR:
W.A. Smith
DATE:
30 October 2015
------------------------------------------------------------------------------*/
#include <avr/io.h>
#include "uart.h"
int main(void)
{
// stores data received from the UART
int rx_data;
// initialize the UART to the baud rate set in global.h
UartInit();
// test the UartTxString() and UartTxByte() functions
UartTxString("Start of UART driver test program.\r\n");
UartTxString("Transmitting a character:");
UartTxByte('C');
UartTxString("\r\nEchoing typed characters:\r\n");
}
// echo back any character typed in the terminal window and terminate with
// carriage return and newline characters
while(1) {
// get a byte from the UART if available
rx_data = UartRxByte();
// was a byte received
if (rx_data > -1) {
// echo the received byte back over the UART
UartTxByte(rx_data);
// terminal window cursor to new line
UartTxString("\r\n");
}
}
global.h
#ifndef GLOBAL_H_
#define GLOBAL_H_
// microcontroller clock frequency
#define F_CPU 16000000UL
// USART baud rate settings
#define BAUD 38400
#define BAUD_TOL 1
#endif /* GLOBAL_H_ */
● 302
Chapter 17 • C Projects
uart.c
/*-----------------------------------------------------------------------------FILE NAME:
uart.c
DESCRIPTION:
UART driver functions for UART0 on Arduino Uno and MEGA
NOTES:
Set the baud rate and clock frequency in global.h
AUTHOR:
W.A. Smith
DATE:
30 October 2015
------------------------------------------------------------------------------*/
#include "global.h"
#include <avr/io.h>
#include <util/setbaud.h>
#include "uart.h"
/*-----------------------------------------------------------------------------FUNCITON NAME: UartInit()
PURPOSE:
Initializes UART0 to the baud rate set in global.h
USAGE:
Call once at the start of a program before using other
UART functions
------------------------------------------------------------------------------*/
void UartInit(void)
{
// set UART0 baud rate
UBRR0H = UBRRH_VALUE;
UBRR0L = UBRRL_VALUE;
// dedicate pins to USART0 for transmit and receive
UCSR0B = (1 << RXEN0) | (1 << TXEN0);
// 8-bit, 1 stop bit, no parity, asynchronous UART
UCSR0C = (1 << UCSZ01) | (1 << UCSZ00);
}
/*-----------------------------------------------------------------------------FUNCITON NAME: UartTxByte()
PURPOSE:
Transmits a single byte from the UART
INPUTS:
data - data byte or character to transmit
------------------------------------------------------------------------------*/
void UartTxByte(char data)
{
while(!(UCSR0A & 0x20));
// wait until ready to tx
UDR0 = data;
// transmit byte
}
/*-----------------------------------------------------------------------------FUNCITON NAME: UartTxString()
PURPOSE:
Transmits a null terminated string from the UART
INPUTS:
str - a pointer to the string to transmit
------------------------------------------------------------------------------*/
void UartTxString(char *str)
{
uint16_t str_index = 0;
while (str[str_index] != 0) {
// exit on null terminator
while(!(UCSR0A & 0x20));
// wait until read to tx
● 303
C Programming with Arduino
}
}
UDR0 = str[str_index++];
// transmit the byte
/*-----------------------------------------------------------------------------FUNCITON NAME: UartRxByte()
PURPOSE:
Receives a single byte from the UART if available
RETURNS:
The data byte or character received if available
Returns -1 if no data byte or character was received
------------------------------------------------------------------------------*/
int UartRxByte(void)
{
int data;
if (UCSR0A & 0x80) {
data = UDR0;
}
else {
data = -1;
}
}
// check if byte received
// get received byte
// byte not received
return data;
uart.h
/*-----------------------------------------------------------------------------FILE NAME:
uart.h
DESCRIPTION:
Header file for uart.c
AUTHOR:
W.A. Smith
DATE:
30 October 2015
------------------------------------------------------------------------------*/
#ifndef UART_H_
#define UART_H_
void UartInit(void);
void UartTxByte(char data);
void UartTxString(char *str);
int UartRxByte(void);
#endif /* UART_H_ */
● 304
Chapter 17 • C Projects
Example Program Output
Start of UART driver test program.
Transmitting a character:C
Echoing typed characters:
a
b
c
d
e
f
g
1
2
3
4
Creating and using the project
The uart_driver project is created without the use of the template file. When connecting
Tera Term to the port of the Arduino board that runs this code, change the baud rate in
Tera Term to 38400. Previous projects all used a baud rate of 9600, but by speeding up
the baud rate the microcontroller will take less time to send and receive data. Baud rate
settings can be found by using the top Tera Term menu and selecting Setup → Serial
port... to open up the serial port setting dialog box.
main.c code
Code in main.c should be easy to understand as it works much the same as the code in
the serial_port project from section 10.5.2 on page 184, with the main differences being
that a new function named UartTxString() is called that transmits an entire string, and
when a character is typed in the terminal window, the cursor of the terminal window is
moved to a new line after echoing the character. The idea of this project is to create a
set of serial port driver functions that can be used in other projects by copying the driver
function files to the other projects. The code in main.c simply tests the driver functions
and demonstrates their usage.
Definitions in global.h and including global.h
The file global.h contains the F_CPU microcontroller clock frequency setting that we
have used before and also contains the baud rate settings for the UART. When global.h
is included in a C source code file it is important to include it before any other included
files because other included files may need the settings that are defined in global.h. The
compiler works through a file from top to bottom, so if it finds the F_CPU setting being
used in an included header file before it knows what value of F_CPU was defined as, it will
generate a warning. This will occur if global.h is included below any file that uses it.
Header file preprocessor directives
Atmel Studio automatically adds preprocessor directives to header files that are created
in Atmel Studio and added to a project. The preprocessor directives prevent definitions or
function prototypes in a header file from being declared more that once which can happen
if the header file is included in a C source file more than once. Code added to the header
file should be placed between the preprocessor directives as shown below.
● 305
C Programming with Arduino
#ifndef GLOBAL_H_
#define GLOBAL_H_
// add header file code here
#endif /* GLOBAL_H_ */
Preprocessor directive #ifndef means "if not defined". In the above code, GLOBAL_H_ will
not be defined when the header file is included in a C source code file for the first time.
#ifndef GLOBAL_H_ will therefore be true and the next preprocessor directive will be
evaluated which defines GLOBAL_H_. All code between the opening #ifndef and closing
#endif will be included in the C source file. If the header file is included in the same C
source file a second time, #ifndef GLOBAL_H_ will then evaluate to false and nothing
between the opening #ifndef and closing #endif will be evaluated or included in the C
source file.
uart.c functions
All of the serial port driver functions are found in the uart.c file and are the same
functions from the serial_port project in section 10.5.2 on page 184 except for a new
function called UartTxString() that transmits a null-terminated string passed to it over
the UART serial port. UartTxString() checks for the null-terminator in the expression of
the while loop. If the null-terminator character is found, the function returns. If the nullterminator character is not found, the character from the string that the variable
str_index is indexing in the string is sent over the serial port.
uart.h
The uart.h file contains the function prototypes for the functions in the uart.c file. Any file
that uses a function from uart.c must include uart.h.
17.2 • Converting Numbers to Strings
When using the serial port drivers instead of printf() to send a string over the serial
port, there are no formatting options available for printing numbers. The project in this
section shows how to write functions to convert numbers stored as variables or constants
to strings that will print the numbers in decimal or hexadecimal format. Before looking at
the actual project code, we explore some functions from the AVR GCC C library that can
be used for the same purpose.
17.2.1 • Using AVR C Library Functions
C library functions are available to convert numbers into ASCII text strings. We have
already looked at the sprintf() function that has all of the formatting options of
printf(), but if used will probably make code size as big as if printf() was used. Another
function, called itoa(), can be used to convert integer numbers to ASCII strings, but it
is not a standard C function and is not defined in the C standard specification. Some C
compilers do include itoa(), as does the AVR GCC compiler used by Atmel Studio.
The itoa project demonstrates the use of the itoa() function. Create this project without
the template file, but add the serial port driver files to the project from the previous
project.
● 306
Chapter 17 • C Projects
Project name: itoa
Project location: CH17\itoa
main.c
#include <avr/io.h>
#include <stdlib.h>
#include "uart.h"
int main(void)
{
int val = 23;
char str_val[30] = {0};
UartInit();
UartTxString("Number in decimal: ");
itoa(val, str_val, 10);
UartTxString(str_val);
UartTxString("\r\n");
UartTxString("Number in hexadecimal: 0x");
itoa(val, str_val, 16);
UartTxString(str_val);
UartTxString("\r\n");
}
while(1);
Program Output
Number in decimal: 23
Number in hexadecimal: 0x17
The three serial port driver files can be added to the project by creating them as new
files in the new project and then copying and pasting the code from the same files in the
previous project, but there is an easier way of adding the files to a new project. Copy
the three files, namely global.h, uart.h and uart.c from the previous project to the new
project. This means copying them from the uart_driver\uart_driver\ folder to the itoa\
itoa folder using the Windows file manager as shown in Figure 17-1.
Figure 17-1 Copying Files Between Projects
● 307
C Programming with Arduino
After the files have been copied to the new project, they must be added to the project
in Atmel Studio. Right-click the project name in Solution Explorer and select Add →
Existing Item... from the pop-up menu as shown in Figure 17-2.
In the Add Existing Item dialog box that opens, all three files can be added to the
project at the same time by holding down the Ctrl key on the keyboard while selecting
each file with the mouse as shown in Figure 17-3 on page 309.
After the serial port driver files have been added to the project, the code from the above
listing can be added to main.c and the project built.
The project uses the integer to ASCII conversion function itoa() to convert the value
of a variable to a string in decimal and hexadecimal format. The output from the itoa()
function is a string that is stored in the str_val string array. This string array is passed
to UartTxString() so that it can be sent to the terminal window.
Passing 10 to the itoa() function as the third parameter lets the function know that we
want to convert the number passed as the first parameter to a string using the base 10
decimal system. Passing 16 as the third parameter converts the number to a base 16
hexadecimal string.
Figure 17-2 Adding Existing Files to a Project
There are a number of other functions related to itoa() that convert different sized
integers to ASCII strings as listed below. All of these functions have their prototypes
declared in the stdlib.h header file.
● 308
Chapter 17 • C Projects
•
itoa() – converts an integer to a string
•
ltoa() – converts a long integer to a string
•
utoa() – converts an unsigned integer to a string
•
ultoa() – converts an unsigned long integer to a string
Each of these functions are used in the same manner, but act on different data types.
They are passed the following arguments when called as the example for itoa() below
shows.
itoa(int val, char *s, int radix);
Where val is the number to convert to a string, s is a pointer to a string that will store
the output string from the function and radix is the base of the number system to
convert to. A binary number can also be printed by these functions by setting the radix
to a value of 2.
Figure 17-3 Selecting Multiple Existing Files to Add to a Project
17.2.2 • Writing Number to String Conversion Functions
Two functions in the num2str project listed below demonstrate one solution to converting
numbers to strings for printing if the compiler being used does not include these
functions. The first function converts a number to a string in decimal format and the
second function converts a number to a string in hexadecimal format.
Create the project without the template file and add the serial port driver files to the
project as done in the previous project. Code in the main.c file demonstrates the use of
the number to string conversion functions found in the file num2str.c that has the same
name as the project. Altogether the project consists of six source code files, namely
main.c, num2str.c, num2str.h, uart.c, uart.h and global.h.
The number to string conversion functions in this project are not the most efficient
● 309
C Programming with Arduino
functions in terms memory usage and number of lines of C code, but are easier for
beginner C programmers to understand. That being said, the code will still be smaller
than using functions such as printf(). The functions have also been written to store
the intermediate values from calculations in variables so that they are easier to view in a
debugger. If you are having trouble understanding the code, it is a worthwhile exercise
to step through the conversion functions using the debugger in Atmel Studio using a
debugging device such as the AVR Dragon. Put watches on the variables that you would
like to see and single-step through the code to see what each line of code does. Another
worthwhile exercise is to search the Internet for C code that does the same or similar
conversion and to see how it works.
Code listings below are printed without the file header comments and top function
comments to save space in the book. The same code with the accompanying files has the
top comments included.
Project name: num2str
Project location: CH17\num2str
main.c
#include
#include
#include
#include
<avr/io.h>
<stdlib.h>
"uart.h"
"num2str.h"
int main(void)
{
char str_val[30] = {0};
UartInit();
UartTxString("Decimal Numbers:\r\n");
Int2DecStr(-2923, str_val);
UartTxString(str_val);
UartTxString("\r\n");
Int2DecStr(0, str_val);
UartTxString(str_val);
UartTxString("\r\n");
Int2DecStr(32767, str_val);
UartTxString(str_val);
UartTxString("\r\n");
Int2DecStr(9002, str_val);
UartTxString(str_val);
UartTxString("\r\n\r\n");
UartTxString("Hexadecimal Numbers:\r\n");
UInt2HexStr(0x1A2B, str_val);
UartTxString(str_val);
UartTxString("\r\n");
UInt2HexStr(0, str_val);
UartTxString(str_val);
UartTxString("\r\n");
UInt2HexStr(0xFFFF, str_val);
UartTxString(str_val);
UartTxString("\r\n");
UInt2HexStr(30123, str_val);
UartTxString(str_val);
UartTxString("\r\n\r\n");
}
while(1);
● 310
Chapter 17 • C Projects
num2str.h
#ifndef NUM2STR_H_
#define NUM2STR_H_
void Int2DecStr(int num, char *str);
void UInt2HexStr(int num, char *str);
#endif /* NUM2STR_H_ */
num2str.c
void Int2DecStr(int num, char *str)
{
// stores individual digit from number
char digit;
// stores number as reversed string
char temp_str[10];
// index into temp_str
int temp_index = 0;
// index int str
int str_index = 0;
}
// check if number is negative
if (num < 0) {
// add sign to string if number is negative
str[str_index++] = '-'; // change number to positive number
num = -num;
}
do {
// get smallest digit
digit = num % 10;
temp_str[temp_index++] = digit + 0x30; // convert digit to ASCII
num /= 10;
// remove smallest digit
} while (num != 0);
temp_index--;
// point index to last digit in temp string
// reverse the reversed temporary string and save in str
while (temp_index >= 0) {
str[str_index++] = temp_str[temp_index--];
}
// terminate the string
str[str_index] = 0;
void UInt2HexStr(unsigned int num, char *str)
{
// stores individual digit from number
char digit;
// stores number as reversed string
char temp_str[10];
// index into temp_str
int temp_index = 0;
// index int str
int str_index = 0;
}
do {
digit = num & 0x0F;
if (digit > 9) {
// convert digit to letter from A to F
temp_str[temp_index++] = digit + 55;
}
else {
// convert digit to ASCII from 0 to 9
temp_str[temp_index++] = digit + 48;
}
num >>= 4;
} while (num > 0);
temp_index--;
// point index to last digit in temp string
// reverse the reversed temporary string and save in str
while (temp_index >= 0) {
str[str_index++] = temp_str[temp_index--];
}
str[str_index] = 0;
// terminate the string
● 311
C Programming with Arduino
uart.c, uart.h and global.h
Copy these serial port driver files and add them to the project.
Program Output
Decimal Numbers:
-2923
0
32767
9002
Hexadecimal Numbers:
1A2B
0
FFFF
75AB
The main program calls the integer to decimal string conversion function Int2DecStr()
and the unsigned integer to hexadecimal conversion function UInt2HexStr() several
times to demonstrate their usage.
Int2DecStr()
To convert an integer number to a string that is an ASCII string representation of the
number in decimal format, each digit of the number in decimal format must be converted
to its ASCII code equivalent. The technique used by the Int2DecStr() function to get
each individual decimal digit of the number is to use the modulus operator and the
division operator. When the modulus operator is used as shown in the line of code below,
the remainder of dividing the number by ten is saved in the digit variable.
digit = num % 10;
A decimal number consists of units, tens, hundreds, thousands, etc. Every part of the
decimal number is divisible by ten, except the units which will be the remainder when the
number is divided by 10. If num in the above line of code has a value of 2923, then 3 is
the result of the above modulus operation. This has the effect of isolating the units part of
the number. In the ASCII table, numbers start at 0x30, so to adjust the number so that it
represents the equivalent ASCII character, we add 0x30 to it as shown in the line of code
below. In this example, we add 0x30 to 3 to get 0x33 which represents the ASCII
character ‘3’ as can be seen in the ASCII table in Appendix A on page 329.
temp_str[temp_index++] = digit + 0x30; // convert digit to ASCII
In the above line of code, the ASCII character is saved to the beginning of the temp_
str array. The only problem with this is that the last digit of the number is stored at the
beginning of the array. As ASCII digits of the number are added to the array, it has the
effect of reversing the number. The number 2923 will be saved as the string 3292 in the
array.
After separating the units from the number 2923 in this example, this number is divided
by ten so that the tens part of the number now becomes the new units part of the
number. Because we are using integers, the number becomes 292 instead of 292.3 as the
fractional part of the number is discarded. After dividing the number by 10, the
do-while loop repeats if the result of the division is not equal to zero. The modulus
● 312
Chapter 17 • C Projects
operator now gets the next part of the number which is 2 and this is then converted to
the ASCII character ‘2’ and added to the next element in the temporary string. The loop
will only terminate when all digits of the number have been converted.
When the loop does terminate, the temporary string temp_str contains a reversed ASCII
representation of the number in decimal format. Code that follows the loop reverses the
reversed string so that it is now the right way around and stores it in the string array str.
If the number to be converted was negative, a negative sign would have been stored at
the beginning of the str array by the first few lines of code in the function, after which
the number is changed to a positive number to make the conversion easier.
UInt2HexStr()
The flow of the UInt2HexStr() function is the same as the Int2DecStr() function in that
a reversed copy of the input number is saved in ASCII format in a temporary string which
is then reversed to get the number in the correct order. The number input to this function
is converted to a hexadecimal representation in an ASCII string.
The number is converted to hexadecimal ASCII digits by firstly isolating the least
significant hexadecimal digit of the number using the bitwise AND operator as shown in
the line of code below.
digit = num & 0x0F;
The above line of code gets the least significant hexadecimal digit by clearing all of the
upper hexadecimal digits from the number. After the single digit has been isolated, it is
converted to its ASCII equivalent by the following code.
if (digit > 9) {
// convert digit to letter from A to F
temp_str[temp_index++] = digit + 55;
}
else {
// convert digit to ASCII from 0 to 9
temp_str[temp_index++] = digit + 48;
}
If the digit has a value greater than nine, 55 decimal is added to it so that it is changed
to the value that represents the letters A to F in the ASCII table. Possible values greater
than nine are 10, 11, 12, 13, 14 and 15. If the value of the digit was 10 and 55 is added
to it, it becomes 65 decimal which has a value of ‘A’ in the ASCII table.
If the digit has a value less than or equal to nine, 48 decimal is added to it which is the
same as adding 0x30. This converts the number to the ASCII values from ‘0’ to ‘9’ as the
Int2DecStr() function did.
After a single digit from the number is converted, which is the least significant
hexadecimal digit, the number is right-shifted by four so that the hexadecimal number to
its left becomes the new least significant hexadecimal digit as shown in the line of code
below.
num >>= 4;
The conversion process repeats converting each digit of the number from LSD to MSD and
saving the result in the temporary string which is then reversed to get the correct number
in ASCII.
● 313
C Programming with Arduino
17.3 • Voltmeter
The voltmeter program uses ADC channel 0 as a voltmeter that can measure voltage
from 0V to 5V and display this measured voltage in the terminal window in the same
way that the example program from Chapter 13, section 13.2 on page 256 did, but this
time the program filters the ADC value by averaging it, and allows the sample rate to be
changed. An LED is used to indicate when the voltage measured is near to the maximum
value that can be measured.
This project reuses code from previous code examples found in this book by breaking
the code up into functions which are put into their own files. The project consists of eight
source code files, namely main.c, adc.c, adc.h, global.h, timer_tick.c, timer_tick.h, uart.c
and uart.h. Files global.h, uart.c and uart.h are reused again to add serial port UART
functionality to the project for printing the calculated voltage to the terminal window. The
timer_tick files add the 1ms timer with ISR to the project. ADC functions are found in the
adc.c file with adc.h header file.
17.3.1 • Voltmeter Project Features
The voltmeter project has the following features:
•
Filters the ADC value by averaging several ADC values
•
Number of ADC values to average can be changed
•
Rate at which ADC values are read can be changed
•
One millisecond timer interrupt is used to generate the ADC sampling rate
•
An LED is switched on when the measured voltage is 4.8V or higher
17.3.2 • Voltmeter Project Code
Create the voltmeter project without the use of the template files. Add the three
UART driver files to the project and create the new source files listed below. Voltage is
measured on the Arduino A0 pin and an LED and series resistor are connected to port pin
PC5 to indicate high voltage.
Project name: voltmeter
Project location: CH17\voltmeter
main.c
#include
#include
#include
#include
#include
#include
#include
<avr/io.h>
<avr/interrupt.h>
<util/atomic.h>
<stdio.h>
"uart.h"
"adc.h"
"timer_tick.h"
int main(void)
{
uint16_t adc_val;
float adc_voltage;
char out_str[30] = {0};
DDRC |= (1 << DDC5);
UartInit();
● 314
// average ADC value
// calculated ADC voltage
// string sent to terminal
// LED on pin PC5
// initialize UART
Chapter 17 • C Projects
AdcInit();
AdcStartConverstion();
UartTxString("\x1B[2J");
UartTxString("\x1B[?25l");
TimerTickInit();
sei();
}
//
//
//
//
//
//
enable ADC
start first ADC conversion
clear screen
hide cursor
enable 1ms tick interrupt
global interrupt enable
while(1) {
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
if (g_buf_full) {
// check if ADC buffer is full
g_buf_full = 0;
// reset buffer flag
// calculate average ADC value
adc_val = AdcCalcAverageVal();
// calculate ADC voltage
adc_voltage = AdcCalcVoltage(adc_val);
// print voltage and average ADC value to terminal
sprintf(out_str, "%4.2fV, %4u\r", adc_voltage, adc_val);
UartTxString(out_str);
// check voltage and switch warning LED on if high
if (adc_voltage >= 4.8) {
PORTC |= (1 << PORTC5);
// warning LED on
}
else {
PORTC &= ~(1 << PORTC5);
// warning LED off
}
}
}
}
adc.c
#include <avr/io.h>
#include "adc.h"
volatile uint16_t g_adc_buffer[ADC_BUF_SZ] = {0};
volatile uint16_t g_adc_buf_idx = 0;
volatile uint8_t g_buf_full = 0;
void AdcInit(void)
{
ADMUX = (1 << REFS0); // 5V supply used for ADC reference, select ADC channel 0
DIDR0 = (1 << ADC0D); // disable digital input on ADC0 pin
// enable ADC, start ADC, ADC clock = 16MHz / 128 = 125kHz
ADCSRA = (1 << ADEN) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
}
void AdcStartConverstion(void)
{
ADCSRA |= (1 << ADSC); // start ADC conversion
}
uint16_t AdcCalcAverageVal(void)
{
uint32_t average = 0;
uint16_t index = 0;
for (index = 0; index < ADC_BUF_SZ; index++) {
average += g_adc_buffer[index];
}
average /= ADC_BUF_SZ;
● 315
C Programming with Arduino
}
return (uint16_t)average;
float AdcCalcVoltage(uint16_t adc_val)
{
return (5.0 * adc_val / 1024.0); // calculate the ADC voltage
}
adc.h
#ifndef ADC_H_
#define ADC_H_
// size of ADC buffer array
#define ADC_BUF_SZ 20
extern volatile uint16_t g_adc_buffer[];
extern volatile uint16_t g_adc_buf_idx;
extern volatile uint8_t g_buf_full;
void AdcInit(void);
void AdcStartConverstion(void);
uint16_t AdcCalcAverageVal(void);
float AdcCalcVoltage(uint16_t adc_val);
#endif /* ADC_H_ */
timer_tick.c
#include
#include
#include
#include
<avr/io.h>
<avr/interrupt.h>
"timer_tick.h"
"adc.h"
void TimerTickInit(void)
{
// set up counter/timer 0 and enable interrupt
OCR0A = 249;
// count value for 1ms period
TCCR0A = (1 << WGM01);
// CTC mode
TCCR0B = (1 << CS01) | (1 << CS00);
// divide by 64 prescaler
TIMSK0 = (1 << OCIE0A);
// enable interrupt
}
// 1ms tick interrupt
ISR(TIMER0_COMPA_vect)
{
static uint8_t time_ms = 0;
time_ms++;
if (time_ms >= SAMPLE_RATE_MS) {
time_ms = 0;
// check if ADC conversion is complete
if (ADCSRA & (1 << ADIF)) {
g_adc_buffer[g_adc_buf_idx++] = ADC; // get the ADC value
// keep buffer index within buffer
if (g_adc_buf_idx >= ADC_BUF_SZ) {
g_adc_buf_idx = 0;
g_buf_full = 1;
// flag full buffer
}
● 316
Chapter 17 • C Projects
}
}
}
// clear the ADIF flag by writing 1 to it
ADCSRA |= (1 << ADIF);
// start the next ADC conversion
AdcStartConverstion();
timer_tick.h
#ifndef TIMER_TICK_H_
#define TIMER_TICK_H_
#define SAMPLE_RATE_MS
10
void TimerTickInit(void);
#endif /* TIMER_TICK_H_ */
uart.c, uart.h and global.h
Copy these serial port driver files and add them to the project.
Example Program Output
3.33V, 682
Example Program Output when printf Linker Flags not Set
?V,
0
17.3.3 • Enabling the printf Family of Functions to Work
Certain linker flags, shown below, must be passed to the linker in order for the printf
functions to work such as the sprintf() function that was used in this project.
-Wl,-u,vfprintf -lprintf_flt
To add the above linker options to the project, open the settings page for the project by
selecting Project → voltmeter Properties... from the top menu in Atmel Studio. In the
left column of the page that opens, click Toolchain to display the settings for the AVR
GCC toolchain. In the pane that appears on the settings page, click Miscellaneous under
the AVR/GNU Linker heading as shown in Figure 17-4 on page 318. Add the above
flags to the Other Linker Flags box as shown in the figure. Save the page and then
compile the project.
17.3.4 • Principle of Operation of the Code
Although the ADC has its own interrupt that will fire when an ADC conversion has taken
place, it was decided to use a timer interrupt to service the ADC by reading the currently
converted ADC value and starting a new ADC conversion in the ISR. This allows the
sample rate of the ADC to be easily changed. In the 1ms timer ISR, a count is checked
to see how many milliseconds have passed since the last ADC conversion. This allows the
● 317
C Programming with Arduino
conversion rate to be changed by changing the value that is compared to the count value.
Each sample or ADC value read in the ISR is placed into a buffer, and when the buffer is
full a flag is set to notify the code running in the while(1) loop that the buffer is ready.
Code in the while(1) loop adds up all of the ADC values in the buffer and then divides
this sum by the size of the buffer so that the average ADC value is calculated. After the
average ADC value is calculated, the voltage that is being measured is calculated. Both
the calculated voltage and the ADC value are then printed to the terminal window.
Figure 17-4 Linker Settings to Enable Printing of Floating Point Numbers
17.3.5 • Examining the Code
main.c
Code at the beginning of main.c calls functions that initialise the hardware used in the
project, namely the output pin that the warning LED is attached to, the USART in UART
mode, the ADC and the 1ms timer with interrupt. The first ADC conversion is also started
and commands are sent to the terminal window to clear the screen and hide the cursor.
All code in the while(1) loop is run inside an atomic block so that the code is not
interrupted and so that the ADC buffer is not written to by the ISR while the values in it
are being averaged. The global variable g_buf_full is used as a flag that is set by the
ISR to indicate when the buffer is full of ADC samples. Only when the buffer is full is this
flag reset and the ADC samples in the buffer averaged. The global variable g_buf_full is
defined in the adc.c file and made available to main.c by declaring it extern in the adc.h
file and including the adc.h file in main.c.
When the buffer is full, two functions called AdcCalcAverageVal() and
● 318
Chapter 17 • C Projects
AdcCalcVoltage() from adc.c are called to calculate the average ADC value and to
calculate the voltage measurement from the average ADC value. After the values have
been calculated they are printed to a string using sprintf() which is then sent to the
terminal window by calling UartTxString().
Finally the calculated voltage value is checked to see if it is greater than or equal to 4.8V.
If the voltage is greater than or equal to 4.8V, the warning LED is switched on to let the
user know that the measured voltage is approaching the maximum voltage that can be
measured which is 5V.
adc.c
Three global variables are defined in adc.c that are used to store ADC values and to
indicate when the buffer used to store the values is full. The g_adc_buffer array is used
as a buffer to store ADC values. The size of the buffer is set by changing the defined
value of ADC_BUF_SZ found in adc.h. g_adc_buf_idx is used as an index into the buffer
and is used by the timer ISR to index each element of the buffer when writing new ADC
values to it. We have already seen g_buf_full used in main() as a flag that indicates
when the buffer is full. All three of these variables are declared extern in adc.h to make
them available to any C source file that includes adc.h.
AdcInit() initialises the ADC, but does not start the ADC conversion. A separate function
called AdcStartConversion() can be called to start each ADC conversion. Function
AdcCalcAverageVal() calculates the average value of all the ADC samples stored in the
buffer and so should only be called when the buffer is full. AdcCalcVoltage() calculates
the voltage from the ADC value that is passed to it which should be the average ADC
value in this program.
It is important to select the correct variable size in an application. Because the buffer that
stores the ADC values can be changed by changing ADC_BUF_SZ in adc.h, the sum of
all of the voltages from the buffer could get large. In AdcCalcAverageVal() the variable
average has been made a 32-bit unsigned integer so that it can hold a large sum. If the
buffer size is made bigger than 64 elements, and all of the values in the buffer were the
maximum ADC value of 1023, the sum of all of the buffer elements would be a number
too big to fit in a 16-bit unsigned integer. In this case, if average was a 16-bit integer it
would then overflow and the incorrect voltage would be calculated. Always check that a
variable is the correct size for what it is used for.
timer_tick.c
The TimerTickInit() function found in timer_tick.c initialises timer/counter 0 to
generate a 1ms timer interrupt as we have done before. When the timer interrupt fires,
the timer interrupt service routine TIMER0_COMPA_vect is run.
The timer ISR is run every millisecond, but the ADC is only read, and a new ADC
conversion started, every 10ms as set by SAMPLE_RATE_MS defined in timer_tick.h. This is
made possible by incrementing static variable time_ms every millisecond and comparing it
with SAMPLE_RATE_MS.
Every 10ms code in the ISR checks if an ADC conversion is complete, and if it is, copies
the ADC value read from the ADC to the g_adc_buffer array. When the buffer array is
full, the g_buf_full flag is set to flag the main program that the buffer is full and ready
to be averaged. AdcStartConverstion() is then called by the ISR to start the next ADC
conversion.
17.3.6 • Code Timing
In embedded programming it is most often important to be aware of the timing of a
program and the timing of hardware devices used in a program. The voltmeter program is
● 319
C Programming with Arduino
a good example for explaining timing of hardware and code.
The ADC is set up to use a prescaler value of 128 which divides the main 16MHz clock
by 128 supplying the ADC with a 125kHz clock. This sets the ADC clock within the 50kHz
to 200kHz range that is necessary to get maximum ADC resolution. According to the
AVR datasheet, a single ADC conversion takes 13 ADC clock cycles. The period of the
125kHz clock is 8µs, so a single ADC conversion will take 104µs (8µs × 13). A single ADC
conversion will therefore be finished a long time before our 10ms sample rate setting.
Using the timer interrupt effectively allows us to reduce the sample rate of the ADC.
The actual update of the display or terminal window will take place after the time it takes
to fill the buffer plus the time it takes to calculate the average ADC value and voltage
and then convert it to a string. With a buffer size of 20 and a sample rate of 10ms, the
average ADC value is calculated every 200ms or five times a second.
It takes approximately 3ms to run the code inside the body of the if statement in the
while(1) loop of main(). Most of this time is used to send the voltage/ADC string to the
terminal window via the UART. This time value was measured by setting an AVR port pin
at the beginning of the code and clearing the pin at the end of the code and then using
an oscilloscope to measure the width of the pulse on the pin. During this time period the
timer interrupt is disabled, so no interrupts occur which is not a problem as we do not
need to keep receiving interrupts in this application. When the interrupts are enabled
again the next set of conversions will take place as usual.
17.4 • Stopwatch
The stopwatch program uses timer/counter 1 on the microcontroller to implement a
stopwatch that counts up in 10ths of a second. A single push-button switch is used to
start and stop the timer. A second push-button switch is used to clear the stopwatch
time which is displayed in the terminal window. Timer/counter 1 is set up to generate an
interrupt every tenth of a second or 100ms which is the timebase that the stopwatch uses
to count from.
17.4.1 • Stopwatch Project Hardware and Code
Create the project without using the template file and add the UART driver files as done in
the previous projects. Create two new files called timer1.c and timer1.h in the project and
add the code listed below to these files.
Push-button switches are connected to port C pins PC4 and PC5. An optional LED is
connected to port C pin PC1 which is toggled by the 100ms timer interrupt, so the circuit
from appendix C can be used, although the extra three LEDs on the port will not be
needed.
Project name: stopwatch
Project location: CH17\stopwatch
main.c
#include
#include
#include
#include
#include
#include
#include
#include
● 320
<avr/io.h>
<util/atomic.h>
<stdlib.h>
<string.h>
"uart.h"
"timer1.h"
"global.h"
<util/delay.h>
Chapter 17 • C Projects
#define BTN_START_STOP (1 << 4)
#define BTN_CLEAR (1 << 5)
void BuildTimeString(char str[], uint32_t seconds, uint8_t tenths);
int main(void)
{
uint8_t timer_stopped = 1;
uint8_t prev_time_s_10ths = 0;
char time_string[15] = {0};
UartInit();
UartTxString("\x1B[2J");
UartTxString("\x1B[?25l");
Timer1Init();
}
// clear screen
// hide cursor
while (1) {
if ((PINC & BTN_START_STOP) && timer_stopped) {
timer_stopped = 0;
Timer1Start();
_delay_ms(40);
// debounce closing switch
while (PINC & BTN_START_STOP); // start timer on open switch
_delay_ms(40);
// debounce opening switch
}
else if ((PINC & BTN_START_STOP) && !timer_stopped) {
timer_stopped = 1;
Timer1Stop();
_delay_ms(40);
// debounce closing switch
while (PINC & BTN_START_STOP); // wait for switch to open
_delay_ms(40);
// debounce opening switch
}
if (PINC & BTN_CLEAR) {
if (timer_stopped) {
prev_time_s_10ths = 0;
g_time_s_10ths = 0;
g_time_s = 0;
UartTxString("\x1B[2J");
// clear screen
Timer1Clear();
}
}
if (g_time_s_10ths != prev_time_s_10ths) {
prev_time_s_10ths = g_time_s_10ths;
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
BuildTimeString(time_string, g_time_s, prev_time_s_10ths);
}
UartTxString(time_string);
}
}
void BuildTimeString(char str[], uint32_t seconds, uint8_t tenths)
{
int string_len;
}
ultoa(seconds, str, 10);
string_len = strlen(str);
str[string_len++] = '.';
str[string_len++] = '0' + tenths;
str[string_len++] = '\r';
str[string_len] = 0;
// put seconds into string
// insert decimal point
// insert tenths of seconds
// terminate string;
● 321
C Programming with Arduino
timer1.c
#include <avr/io.h>
#include <avr/interrupt.h>
volatile uint8_t g_time_s_10ths = 0;
volatile uint32_t g_time_s = 0;
void Timer1Init(void)
{
OCR1A = 24999;
TCCR1B = (1 << WGM12);
TIMSK1 = (1 << OCIE1A);
DDRC |= (1 << 1);
}
//
//
//
//
count for 100ms period
CTC mode,
enable compare A interrupt
LED on PC1
void Timer1Start(void)
{
// selecting the clock source starts the timer
TCCR1B |= (1 << CS11) | (1 << CS10);
// divide by 64 prescaler
sei(); // global interrupt enable
}
void Timer1Stop(void)
{
cli();
// global interrupt disable
// select no clock source to stop timer
TCCR1B &= ~((1 << CS12) | (1 << CS11) | (1 << CS10));
}
void Timer1Clear(void)
{
TCNT1 = 0; // clear the count value
PORTC &= ~(1 << 1); // switch LED off
}
ISR(TIMER1_COMPA_vect)
{
g_time_s_10ths++;
if (g_time_s_10ths >= 10) {
g_time_s_10ths = 0;
g_time_s++;
}
PORTC ^= (1 << 1);
}
// count tenths of a second
// count seconds
// toggle LED
timer1.h
#ifndef TIMER1_H_
#define TIMER1_H_
extern volatile uint8_t g_time_s_10ths;
extern volatile uint32_t g_time_s;
void
void
void
void
Timer1Init(void);
Timer1Start(void);
Timer1Stop(void);
Timer1Clear(void);
#endif /* TIMER1_H_ */
● 322
Chapter 17 • C Projects
uart.c, uart.h and global.h
Copy these serial port driver files and add them to the project.
Example Program Output
24.6
17.4.2 • Stopwatch Project Design
The push-button switch connected to port C pin PC4 is used to start and stop the timer.
When the switch is closed, the timer will immediately start timing. When the switch is
closed a second time the timer will stop, freezing the time period in the terminal window.
After the stopwatch has been stopped, the user can either start it again, in which case the
timer will carry on timing from where it was stopped, or the user can press the second
push-button switch on PC5 to clear the display and reset the timer.
An optional LED is attached to port C pin PC1 and is toggled in the 100ms interrupt
service routine of the timer to give a visual indication that the stopwatch is busy timing.
The time taken to convert the stopwatch time to a string and send it to the terminal
window is much less than the 100ms timer tick, so the code will always update the
display every 100ms without skipping any updates.
A function in main.c is used to convert the timer seconds and 10ths of a second values
to a string to send to the terminal window, so the project does not use printf() or
sprintf() which keeps the memory usage to a minimum.
17.4.3 • Stopwatch Project Code Explanation
Defining button names
Button names for the two push-button switches, which are the bits in the PINC register
for the pins that the switches are connected to, are defined in main.c, giving them
meaningful names as shown below.
#define BTN_START_STOP
(1 << 4)
#define BTN_CLEAR
(1 << 5)
In this code the left-shift operator is used in conjunction with the bit position of the port
pin that the button is attached to, which is perhaps easier than trying to remember the
name of the bit in the port register. The above code is equivalent to using the predefined
bit names PINC4 instead of 4 and PINC5 instead of 5, as they are used to check the
switch statuses in the PINC register.
Stopwatch variables
The following variables are defined in main.c.
uint8_t timer_stopped = 1;
uint8_t prev_time_s_10ths = 0;
char time_string[15] = {0};
The timer_stopped variable is used as a flag to store the current state of the stopwatch.
When this variable holds the value 1, or in other words evaluates to true, it indicates that
the timer is stopped. When it holds a value of 0, or evaluates to false, it indicates that the
● 323
C Programming with Arduino
timer has started. A copy of the 10ths of seconds, which is incremented by the timer ISR,
is kept in prev_time_s_10ths and used to test when the ISR has incremented the global
variable that holds the 10ths of a second. When the current stopwatch time is converted
to a string for sending to the terminal window, it is stored in the time_string array.
The following global variables are defined in timer1.c.
volatile uint8_t g_time_s_10ths = 0;
volatile uint32_t g_time_s = 0;
Each 10th of a second is stored in g_time_s_10ths and incremented by the timer ISR.
This variable will increment from 0 to 9, and when it reaches 10 is set back to 0 and
the variable that holds the seconds is incremented. Seconds are stored in g_time_s and
incremented every ten 10ths of a second.
Program flow
At the beginning of main(), the UART is initialised, the terminal window cleared and the
cursor hidden. Timer 1 is initialised by calling the Timer1Init() function from timer1.c
which sets up the timer to time 100ms intervals and enables the timer interrupt but does
not start the timer. It also sets up port pin PC1 as an output for driving the LED.
In the while(1) loop the code checks to see if either of the two push-button switches is
closed and if the 10ths of a second value has changed. When the 10ths of a second value
does change, the stopwatch value is sent to the terminal window.
Timer 1 ISR
The timer 1 ISR found in timer1.c increments the 10th of a second variable every time
that the interrupt fires, which occurs every 100ms after the timer has been started and
the global interrupt enabled. Every time that the 10th of a second variable reaches a
value of 10, it is reset to 0 and the variable that holds the seconds is incremented by 1.
Therefore once the timer is started, the current 10th of a second value can be found in
g_time_s_10ths and the current seconds value can be found in g_time_s. The LED on
port pin PC1 is also toggled by the timer 1 ISR.
Starting the timer
When code in the while(1) loop starts running, the timer has been initialised, but not
started. If the user presses the push-button switch connected to pin PC4, the if statement
shown below evaluates to true and the timer is started by calling Timer1Start().
if ((PINC & BTN_START_STOP) && timer_stopped) {
timer_stopped = 0;
Timer1Start();
_delay_ms(40);
// debounce closing switch
while (PINC & BTN_START_STOP); // start timer on open switch
_delay_ms(40);
// debounce opening switch
}
Timer1Start() starts the timer by selecting the clock source from the timer prescaler
and also sets the global interrupt flag by calling sei() which enables the timer interrupt.
The above code also updates the timer_stopped flag to indicate that the timer has
started, and debounces the push-button switch. If the start/stop push-button switch on
PC4 is held in by the user, the stopwatch will carry on timing because the main code will
be interrupted and the timer ISR run, but the time will not be displayed in the terminal
window until the user releases the switch. The reason that the time is not displayed in the
terminal window when the switch is held closed is because the code that updates the time
in the terminal window is in the main program loop which is held up by the closed switch.
● 324
Chapter 17 • C Projects
This is not seen as a problem because normal operation of the stopwatch is to push and
immediately release the start/stop button and a user who holds the button in will see that
the display is not updating and should release the button.
Stopping the timer
The following code will only run if the timer is running and the start/stop push-button
switch is closed.
else if ((PINC & BTN_START_STOP) &&
timer_stopped = 1;
Timer1Stop();
_delay_ms(40);
while (PINC & BTN_START_STOP);
_delay_ms(40);
}
!timer_stopped) {
// debounce closing switch
// wait for switch to open
// debounce opening switch
This code updates the timer_stopped flag to indicate that the timer is now stopped. The
timer is then stopped by calling Timer1Stop() which disables the global interrupt and
selects no clock source to stop the timer. The timer is not cleared so that if the timer is
started again it will start timing from where it left off when it was stopped. Again the
push-button switch is debounced so that the program does not receive multiple open and
closed switch states and start and stop the stopwatch accordingly.
Updating the timer display
The following code sends the current stopwatch time to the terminal window only when
the time has changed.
if (g_time_s_10ths != prev_time_s_10ths) {
prev_time_s_10ths = g_time_s_10ths;
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
BuildTimeString(time_string, g_time_s, prev_time_s_10ths);
}
UartTxString(time_string);
}
By comparing a copy of the 10ths of a second value with the current 10ths of a
second value, the program can tell when the ISR has updated the 10ths of a second
variable. When the time has changed, it is converted into a string by calling the
BuildTimeString() function, shown on the next page, and transmitted to the terminal
window by calling UartTxString().
BuildTimeString() builds a string that consists of the seconds and 10ths of a second
separated by a decimal point and followed by a carriage return character. The ultoa()
function is used to convert the seconds part of the time into a string. Because we don’t
know how many characters the string consists of, as the seconds are always increasing
and the number of digits that the number consists of will increase, strlen() is called to
get the string length. The length of the string is needed so that we can append the rest of
the characters to the seconds part of the string.
After getting the string length and storing it in the string_len variable, this variable is
used as an index into the string. It automatically indexes the next character after the
seconds in the string because the elements of the string array are numbered from zero
and not from one.
Array notation combined with string_len used as an index are used to insert the decimal
point ‘.’, tenths of a second, the carriage return character ‘\r’ and the string null
terminator into the string. Because the 10ths of a second value will always be a value
● 325
C Programming with Arduino
from 0 to 9, we can convert it to an ASCII character by adding the character ‘0’ to it
which is the same as adding 0x30 to it.
void BuildTimeString(char str[], uint32_t seconds, uint8_t tenths)
{
int string_len;
}
ultoa(seconds, str, 10);
string_len = strlen(str);
str[string_len++] = '.';
str[string_len++] = '0' + tenths;
str[string_len++] = '\r';
str[string_len] = 0;
// put seconds into string
// insert decimal point
// insert tenths of seconds
// terminate string;
Clearing the timer
When the stopwatch clear push-button switch is closed, the stopwatch time will only be
cleared if the stopwatch is stopped as shown in the following code.
if (PINC & BTN_CLEAR) {
if (timer_stopped) {
prev_time_s_10ths = 0;
g_time_s_10ths = 0;
g_time_s = 0;
UartTxString("\x1B[2J");
Timer1Clear();
}
}
// clear screen
When the timer is stopped and the clear button pushed, all of the variables that are used
to store the time are cleared to zero. The command that clears the terminal window is
sent and the Timer1Clear() function is called. Timer1Clear() clears the actual count
value in the timer register so that the timer will start counting from zero when the
stopwatch is started again. The function also switches the LED off.
Final program notes
A program can be written as simply as possible or made more complex by adding more
features to it. The stopwatch program can have many more features added to it, for
example it could be modified to display hours, minutes and seconds instead of just
seconds and 10ths of a second. It could even be modified to display days as well, if
needed.
After writing a program, it may become obvious that the program can be better broken
up into functions, or simplified, for example in the stopwatch program code used to
debounce the switch is written twice – once for starting the stopwatch and once for
stopping the stopwatch. This code could be put into a function and called twice.
Always remember to comment your code. Many of the examples in this book minimised
the use of comments to reduce the print size of the code and because the code is fully
explained in the book, but in normal programming code should be fully commented. A
mix of commenting styles were also used to demonstrate their use, but it is best to stick
to one style to make all code consistent.
● 326
Chapter 17 • C Projects
17.5 • Where to from here?
I hope that you have enjoyed using this book to learn the C programming language
for embedded systems. Although it would have been nice to have programmed more
hardware devices, limited space in this book as well as an attempt to make the book
accessible to everyone by not requiring special hardware devices prevented this. I will
leave you to explore other hardware devices on your own.
The key to becoming a proficient C programmer is to write programs as well as to read
programs from other authors. Use the Internet to obtain source code that you can read
and learn from. Write lots of your own programs and never be afraid to experiment.
Be sure to look at Appendix D on page 337 that contains useful references to do
with C programming and AVR microcontrollers. Application notes from Atmel for AVR
microcontrollers are often a good source for learning how various microcontroller
peripheral devices work and often contains C code examples.
Good luck! I hope that you spend many enjoyable hours programming embedded
systems in C!
● 327
C Programming with Arduino
● 328
Appendix A • The ASCII Table
Appendix A • The ASCII Table
On Windows computers characters from the extended part of the ASCII table will often
not print correctly because of the font selected by the program using the characters.
Some terminal programs have a default font that does not print the graphical characters
from the ASCII table. This can usually be remedied by selecting the Terminal font if the
program being used allows the font to be changed.
Table A-1 ASCII characters 0 - 127
DEC
HEX
CHAR DEC
HEX
0
0
NULL 32
20
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
1
2
3
4
5
6
7
8
9
0A
0B
0C
0D
0E
0F
10
11
12
13
14
15
16
17
18
19
1A
1B
1C
1D
1E
1F
SOH
STX
ETX
EOT
ENQ
ACK
BEL
BS
HT
LF
VT
FF
CR
SO
SI
DLE
DC1
DC2
DC3
DC4
NAK
SYN
ETB
CAN
EM
SUB
ESC
FS
GS
RS
US
21
22
23
24
25
26
27
28
29
2A
2B
2C
2D
2E
2F
30
31
32
33
34
35
36
37
38
39
3A
3B
3C
3D
3E
3F
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
CHAR DEC
space
!
"
#
$
%
&
'
(
)
*
+
,
.
/
0
1
2
3
4
5
6
7
8
9
:
;
<
=
>
?
HEX
CHAR DEC
HEX
CHAR
64
40
@
96
60
`
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
41
42
43
44
45
46
47
48
49
4A
4B
4C
4D
4E
4F
50
51
52
53
54
55
56
57
58
59
5A
5B
5C
5D
5E
5F
A
B
C
D
E
F
G
H
I
J
K
L
M
N
O
P
Q
R
S
T
U
V
W
X
Y
Z
[
\
]
^
_
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
61
62
63
64
65
66
67
68
69
6A
6B
6C
6D
6E
6F
70
71
72
73
74
75
76
77
78
79
7A
7B
7C
7D
7E
7F
a
b
c
d
e
f
g
h
i
j
k
l
m
n
o
p
q
r
s
t
u
v
w
x
y
z
{
|
}
~
delete
•
0 to 31 are unprintable control codes used for communications
•
32 to 126 are printable ASCII characters and character 127 is unprintable
● 329
C Programming with Arduino
Table A-2 Extended ASCII characters
DEC
HEX
CHAR DEC
HEX
CHAR DEC
HEX
CHAR DEC
HEX
CHAR
128
80
Ç
160
A0
á
192
C0
└
224
E0
α
129
81
ü
161
A1
í
193
C1
┴
225
E1
ß
130
82
é
162
A2
ó
194
C2
┬
226
E2
Γ
131
83
â
163
A3
ú
195
C3
├
227
E3
π
132
84
ä
164
A4
ñ
196
C4
─
228
E4
Σ
133
85
à
165
A5
Ñ
197
C5
┼
229
E5
σ
134
86
å
166
A6
ª
198
C6
╞
230
E6
µ
135
87
ç
167
A7
º
199
C7
╟
231
E7
τ
136
88
ê
168
A8
¿
200
C8
╚
232
E8
Φ
137
89
ë
169
A9
⌐
201
C9
╔
233
E9
Θ
138
8A
è
170
AA
¬
202
CA
╩
234
EA
Ω
139
8B
ï
171
AB
½
203
CB
╦
235
EB
δ
140
8C
î
172
AC
¼
204
CC
╠
236
EC
∞
141
8D
ì
173
AD
¡
205
CD
═
237
ED
φ
142
8E
Ä
174
AE
«
206
CE
╬
238
EE
ε
143
8F
Å
175
AF
»
207
CF
╧
239
EF
∩
144
90
É
176
B0
░
208
D0
╨
240
F0
≡
145
91
æ
177
B1
▒
209
D1
╤
241
F1
±
146
92
Æ
178
B2
▓
210
D2
╥
242
F2
≥
147
93
ô
179
B3
│
211
D3
╙
243
F3
≤
148
94
ö
180
B4
┤
212
D4
╘
244
F4
⌠
149
95
ò
181
B5
╡
213
D5
╒
245
F5
⌡
150
96
û
182
B6
╢
214
D6
╓
246
F6
÷
151
97
ù
183
B7
╖
215
D7
╫
247
F7
≈
152
98
ÿ
184
B8
╕
216
D8
╪
248
F8
°
153
99
Ö
185
B9
╣
217
D9
┘
249
F9
∙
154
9A
Ü
186
BA
║
218
DA
┌
250
FA
·
155
9B
¢
187
BB
╗
219
DB
█
251
FB
√
156
9C
£
188
BC
╝
220
DC
▄
252
FC
ⁿ
157
9D
¥
189
BD
╜
221
DD
▌
253
FD
²
158
9E
₧
190
BE
╛
222
DE
▐
254
FE
■
159
9F
ƒ
191
BF
┐
223
DF
▀
255
FF
● 330
Appendix B • Arduino – AVR Port Pin Mapping
Appendix B • Arduino – AVR Port Pin Mapping
Arduino pin numbers to AVR microcontroller port pin mapping can be found below for the
Arduino Uno and Arduino MEGA 2560.
B.1 • Arduino Uno
Digital Pins
Pin
Alternate Function
Arduino
AVR
Arduino
AVR
0
PD0
RX
PCINT16/RXD
1
PD1
TX
PCINT17/TXD
2
PD2
3
PD3
4
PD4
5
PD5
PWM
PCINT21/OC0B/T1
6
PD6
PWM
PCINT22/OC0A/AIN0
7
PD7
8
PB0
9
PB1
PWM
OC1A/PCINT1
10
PB2
PWM
SS/OC1B/PCINT2
11
PB3
PWM
MOSI/OC2A/PCINT3
12
PB4
13
PB5
PCINT18/INT0
PWM
PCINT19/OC2B/INT1
PCINT20/XCK/T0
PCINT23/AIN1
PCINT0/CLKO/ICP1
MISO/PCINT4
L LED
SCK/PCINT5
Analogue Pins
Pin
Alternate Function
Arduino
AVR
Arduino
AVR
A0
PC0
ADC0/PCINT8
A1
PC1
ADC1/PCINT9
A2
PC2
ADC2/PCINT10
A3
PC3
ADC3/PCINT11
A4
PC4
ADC4/SDA/PCINT12
A5
PC5
ADC5/SCL/PCINT13
● 331
C Programming with Arduino
B.2 • Arduino MEGA
Digital Pins
Pin
Alternate Function
Arduino
AVR
Arduino
AVR
0
PE0
RX0
RXD0/PCINT8
1
PE1
TX0
TXD0
2
PE4
PWM
OC3B/INT4
3
PE5
PWM
OC3C/INT5
4
PG5
PWM
OC0B
5
PE3
PWM
OC3A/AIN1
6
PH3
PWM
OC4A
7
PH4
PWM
OC4B
8
PH5
PWM
OC4C
9
PH6
PWM
OC2B
10
PB4
PWM
OC2A/PCINT4
11
PB5
PWM
OC1A/PCINT5
12
PB6
PWM
OC1B/PCINT6
13
PB7
L LED/PWM
OC0A/OC1C/PCINT7
14
PJ1
TX3
TXD3/PCINT10
15
PJ0
RX3
RXD3/PCINT9
16
PH1
TX2
TXD2
17
PH0
RX2
RXD2
18
PD3
TX1
TXD1/INT3
19
PD2
RX1
RXD1/INT2
20
PD1
SDA
SDA/INT1
21
PD0
SCL
SCL/INT0
22
PA0
AD0
23
PA1
AD1
24
PA2
AD2
25
PA3
AD3
26
PA4
AD4
27
PA5
AD5
28
PA6
AD6
29
PA7
AD7
30
PC7
A15
31
PC6
A14
32
PC5
A13
33
PC4
A12
● 332
Appendix B • Arduino – AVR Port Pin Mapping
Digital Pins
Pin
Alternate Function
Arduino
AVR
34
PC3
Arduino
AVR
A11
35
PC2
A10
36
PC1
A9
37
PC0
A8
38
PD7
T0
39
PG2
ALE
40
PG1
RD
41
PG0
WR
42
PL7
43
PL6
44
PL5
OC5C
45
PL4
OC5B
46
PL3
OC5A
47
PL2
T5
48
PL1
ICP5
49
PL0
ICP4
50
PB3
MISO/PCINT3
51
PB2
MOSI/PCINT2
52
PB1
SCK/PCINT1
53
PB0
SS/PCINT0
Analogue Pins
Pin
Alternate Function
Arduino
AVR
Arduino
AVR
A0
PF0
ADC0
A1
PF1
ADC1
A2
PF2
ADC2
A3
PF3
ADC3
A4
PF4
ADC4/TCK
A5
PF5
ADC5/TMS
A6
PF6
ADC6/TDO
A7
PF7
ADC7/TDI
A8
PK0
ADC8/PCINT16
A9
PK1
ADC9/PCINT17
A10
PK2
ADC10/PCINT18
● 333
C Programming with Arduino
Analogue Pins
Pin
Alternate Function
Arduino
AVR
A11
PK3
ADC11/PCINT19
A12
PK4
ADC12/PCINT20
A13
PK5
ADC13/PCINT21
A14
PK6
ADC14/PCINT22
A15
PK7
ADC15/PCINT23
● 334
Arduino
AVR
Appendix C • Standard Arduino I/O Circuit
Appendix C • Standard Arduino I/O Circuit
The following circuit for the Arduino Uno and MEGA is used by many of the code examples
in this book – LEDs on pins PC0 to PC3, switches on PC4 and PC5. Don’t forget to connect
the common GND to one of the Arduino GND pins.
C.1 • Arduino Uno
Figure C-1 Circuit for the Arduino Uno
● 335
C Programming with Arduino
C.2 • Arduino MEGA
Figure C-2 Circuit for the Arduino MEGA
● 336
Appendix D • References and Resources
Appendix D • References and Resources
The following references and resources are useful when programming AVR
microcontrollers, especially the AVR datasheets and AVR GCC C library reference.
D.1 • Book Website and Files
The accompanying website for this book can be found at wspublishing.net/avr-c
All the code examples found in this book can be downloaded from www.elektor.com
D.2 • Datasheets and Application Notes
Datasheets for AVR microcontrollers can be found on the Atmel website at:
www.atmel.com
ATmega328P datasheet for Arduino Uno:
www.atmel.com/devices/ATMEGA328P.aspx
ATmega2560 datasheet for Arduino MEGA 2560:
www.atmel.com/devices/ATMEGA2560.aspx
Click the Documents tab on either of the above two pages to see additional
documentation and application notes for these microcontrollers.
D.3 • AVR Libc Reference
The AVR Libc C library documentation can be found at www.nongnu.org/avr-libc/ where the
Libc manual can be downloaded in HTML or PDF format. The manual can also be viewed
online.
User manual online: www.nongnu.org/avr-libc/user-manual/pages.html
Library reference online: www.nongnu.org/avr-libc/user-manual/modules.html
D.4 • AVR Forum
The AVR Freaks forum contains many AVR related topics and answers to AVR and AVR C
programming questions. The forum can be found at www.avrfreaks.net
D.5 • Additional Resources
AVR tutorials can be found on the AVR Freaks website at:
www.avrfreaks.net/forums/tutorials
Several useful AVR related articles can be found on Dean Camera’s website at:
www.fourwalledcubicle.com/AVRArticles.php
● 337
C Programming with Arduino
● 338
Index
Index
Symbols
|= 238, 242
|| 239, 242
~ 229, 242
^ 229, 242
8-bit AVR double 199
^= 238, 242
8-bit AVR long double 199
- 51, 242
%c 141
-- 242
#define 99, 110
-= 238, 242
%d or %i 141
-> 242
#include 102, 110
: 242
%u 141
! 239, 242
%x 141
!= 62, 242
%X 141
? 242
. 242
() 242
[] 242
* 51, 242
*= 238, 242
/ 51, 242
/= 238, 242
& 229, 242
&& 239, 242
&= 238, 242
A
ADC 145, 256
Adding Watches 271
Additional Resources 337
Addresses 133
Algorithm 89
AND Bitwise Operator 167
Application Notes 337
Arduino Board 21
Arduino I/O Circuit 335
% 51, 242
Arduino MEGA 2560 20, 21, 145, 147,
152, 332
%= 238, 242
Arduino MEGA 2560 Circuit 154
+ 51, 242
Arduino MEGA AVR 153
++ 242
Arduino Uno 146, 152, 331
+= 238, 242
Arduino Uno AVR 153
< 62, 242
Arduino Uno Circuit 154
<< 229, 242
Arithmetic Operators 49
<<= 238, 242
array definition 205
<= 62, 242
Array Element Addresses 214
= 238, 242
Array of Integers 207
== 62, 242
Arrays 205
> 62, 242
ASCII Table 124, 329
>= 62, 242
Assignment Operators 238
>> 229, 242
ATmega328P 21, 34, 142, 145, 149
>>= 238, 242
ATmega2560 34, 142, 147, 150
| 229, 242
● 339
C Programming with Arduino
ATmega2560 Ports 146
continue 178, 179, 296
Atmel Studio 17, 25
default 192, 296
AVR Architecture 17
do 178, 180, 181, 202, 296
AVR C Library 306
double 199, 200, 296
AVR Dragon 32, 267
else 63, 64, 67, 68, 296
AVR Forum 337
enum 292, 296
AVRISP 32
extern 294, 296, 299
AVR Libc Reference 337
float 48, 285, 289, 295, 296
AVR Memory Map 169
for 296
AVR microcontroller 17, 41, 163, 301
goto 295, 296, 299
AVR Pins and Ports 145
if 63, 293, 295, 296, 320
AVR Port Pin Mapping 331
int 92, 98, 124, 138, 296
B
long 138, 141, 142, 296
Baud Rate 186
Binary Basics 111
binary digit 111
binary numbers 111, 113
Bitwise NOT Operator 234
Bitwise XOR Operator 235
Boolean Bitwise Operators 229
bounce 223
braces {} 40
break Statement 179
Button Message 226
Button Wait 227
bytes 111, 130
register 133, 137, 231, 243, 296
return 95, 96, 296
short 139, 141, 296
signed 139, 141, 296
sizeof 139, 140, 208, 226, 296
static 294, 296, 319
struct 286, 288, 296
switch 293, 296
typedef 293, 294, 296, 299
union 288, 296, 299
unsigned 293, 294, 296, 298
void 36, 91, 296
volatile 98, 296
while 178, 180, 202, 296
C
Class Specifiers 294
C99 Variable Types 298
clock cycle 251
Casts 200
C Macros 246
char 58
Code Listings 30
Characters 35
Code Timing 319
Character Variables 48
Commenting Programs 79
C Keywords 296
Communication Parameters 186
auto 294, 296
Comparative Operators 61, 72
break 178, 179, 192, 202, 296
Compiler Optimisation 269
case 192, 296
Compile-time 247
char 58, 231, 285, 296, 298
Compiling 52
const 295, 296, 299
Conditional Operator 193
● 340
Index
continue Statement 179
Flash memory 133, 170
Continuous Loop 298
float 58, 199
control bus 130
floating point 103
convert from binary to hexadecimal 119
Floating Point Data Types 199
C Projects 301
Floating Point Variables 48
D
for Loop 181
data bus 130
Data Direction Register 148
Data memory 170
Format Specifiers 173
Functions 36, 194
Functions Calling 103
Datasheets 337
G
Debugging 22, 267
GCC compiler 218
debugWIRE 267, 269
GCC toolchain 89, 107
Decimal Numbers 112
Global Variables 197
Decisions 62
GNU C compiler 138
Decrement Operators 159
goto Statement 295
Design Notes 301
H
double 199
double forward slash 81
do – while Loop 180
Driver Code 302
DWEN 269
E
ELF format 53
else 63
else - if 67
Embedded Systems 18
Enabling ISP 277
Enumerated Type 291
Errors 53
Escape Sequences 176
Extended ASCII characters 330
Handling Interrupts 279
Hardware Registers 271
Header Files 106, 245, 305
Hexadecimal Numbers 116, 121
High 70
I
ICSP header 147, 267
IDE 17
if 63, 185
Increment Operators 159
Input 41, 46
Input Pins 163, 166
int 58, 138
Integer Data Types 138
Integer Format Specifiers 141
F
Interrupt Driven Hardware 255
False 61, 70
Interrupts 279
Field Width 174
Invalid variable names 58
Field Width Specifiers 51
I/O 163
Files 337
I/O Ports 262
Flashing LED 97, 151
ISP 269
● 341
C Programming with Arduino
ISRs 281
non-zero value 72
K
O
keywords 36, 72
Operator Precedence 241
L
Output 41
least significant bit (LSB) 111
Output pins 166
least significant digit (LSD) 111
P
Left Shift Operators 236
parentheses () 40
Library Files 107
peripheral devices 130
Library String Functions 213
Peripherals 133
Link Errors 56
pointer notation 217
Linking 52
Pointers 134, 194
Local Variables 196
Polled Timer 252
Logical Operators 239
Port Pin 163
logic state 165
Post-decrement 159
long 138
Post-increment 159
long Data Type 98
Pre-decrement 159
long double 199
Pre-defined Bit Names 243
Loops 178
Pre-increment 159
Low 70
Preprocessor Directives 37, 296
M
printf() 36
main() 36
mathematical constant 101
Program Statements 37
Pull-up Resistors 263
Memory 129, 133
R
Memory Chip 130, 132
RAM 129
Memory Maps 163
Receiving a Byte 187
Memory Sizes 142
References 337
Microcontroller 18, 130
Register Functionality 262
Microprocessors 18, 133
Registers 186
most significant bit (MSB) 111
Resources 337
most significant digit (MSD) 111
Right Shift Operators 236
Multidimensional Arrays 221
RISC 17
Multiple Source Files 103
ROM 129
N
Run-time 247
Nested Loops 188
S
Nesting Decisions 189
scanf() 44
New Project 27
Serial Port 182, 184
nibble 111
Serial Port Driver 301
● 342
Index
Setting Breakpoints 274
UART 145, 147, 301
Shift Operators 229
UartInit() 36
short 138
Unions 288
signed char 138
unsigned char or char 138
significant bit 111
unsigned int 138
Single Stepping 271
unsigned long 138
Solution Explorer 28, 105
unsigned short 138
specific address 133
USART 183, 301
SRAM 130, 133, 170
USB 147, 183, 301
Static Variables 198
V
Stopping Debugging 274
Stopwatch 301, 320
String Conversion Functions 309
Strings 35, 209
Structures 285, 287
Style 81
subroutines 36
switch Statement 190
T
Tab Settings 81
Tab Width 81
Template Files 26
Tera Term 211
Terminal Control Codes 101
Terminal Emulator 26
Valid variable names 58
Variable Compare 70
Variables 41, 43
Viewing Pin State 275
Viewing Variables 273
Voltmeter 314
W
Warnings 53
Watchdog Timer 260
Websites 337
while(1) 76, 98, 151
while Loop 73
Whitespace Characters 37
word 111
Tick Interrupt 281
Time Delay 252
Timer Counter 251
Timer Function 255
Timer Interrupt 279
Toolchain 17, 52
Transmitting a Byte 187
True 61, 70
Truth Tables 230
two-dimensional array 222
typedef Declarator 293
Type Qualifiers 295
U
● 343
Warwick A. Smith
Warwick A. Smith lives in
South Africa and works
as an Electronics Engineer
and Embedded System
Programmer. He is a
bestselling author of the
books C Programming for
Embedded Microcontrollers, ARM Microcontroller
Interfacing and Open
Source Electronics on
Linux.
ISBN 978-1-907920-46-2
Arduino is the hardware platform used to teach the C
programming language as Arduino boards are available
worldwide and contain the popular AVR microcontrollers from
Atmel.
Atmel Studio is used as the development environment for
writing C programs for AVR microcontrollers. It is a full-featured
integrated development environment (IDE) that uses the GCC C
software tools for AVR microcontrollers and is free to download.
•
Start learning to program from the very first chapter
•
No programming experience is necessary
•
Learn by doing - type and run the example programs
•
A fun way to learn the C programming language
•
Ideal for electronic hobbyists, students and engineers
wanting to learn the C programming language in an
embedded environment on AVR microcontrollers
•
Use the free full-featured Atmel Studio IDE software for
Windows
•
Write C programs for 8-bit AVR microcontrollers as found
on the Arduino Uno and MEGA boards
•
Example code runs on Arduino Uno and Arduino MEGA
2560 boards and can be adapted to run on other AVR
microcontrollers or boards
•
Use the AVR Dragon programmer / debugger in conjunction
with Atmel Studio to debug C programs
DESIGN
www.elektor.com
Technology is constantly changing. New microcontrollers
become available every year. The one thing that has stayed
the same is the C programming language used to program
these microcontrollers. If you would like to learn this standard
language to program microcontrollers, then this book is for you!
AVR MICROCONTROLLERS AND ATMEL STUDIO FOR
C
PROGRAMMING
WITH ARDUINO
LEARN
Elektor International Media BV
C
PROGRAMMING
WITH ARDUINO
C PROGRAMMING WITH ARDUINO ● WARWICK A. SMITH
AVR MICROCONTROLLERS AND ATMEL STUDIO FOR
Warwick A. Smith
LEARN
DESIGN
SHARE
SHARE
LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● S
● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE
GN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ●
LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ●
● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE ● LEARN ● DESIGN ● SHARE
Puede agregar este documento a su colección de estudio (s)
Iniciar sesión Disponible sólo para usuarios autorizadosPuede agregar este documento a su lista guardada
Iniciar sesión Disponible sólo para usuarios autorizados(Para quejas, use otra forma )