Programming the Finite State Machine with 8-Bit PICs in Assembly and C 9781907920929

Andrew Pratt provides a detailed introduction to programming PIC microcontrollers, as well as a thorough overview of the

309 107 57MB

English Pages 188 Year 2023

Report DMCA / Copyright

DOWNLOAD FILE

Polecaj historie

Programming the Finite State Machine with 8-Bit PICs in Assembly and C
 9781907920929

Table of contents :
Programming the Finite State Machine
Table of Contents
Preface
Chapter 1 • Getting Started
1.1 • Introduction
1.2 • Practical Implementation
1.3 • Some Fundamentals
1.4 • Program Memory
1.5 • Hello World
Chapter 2 • The Assembly Program as a Finite State Machine
2.1 • Introduction
2.2 • A State Machine Framework for Assembly Language
2.3 • Timer0
2.4 • Interrupts
2.5 • A More Complicated LED Flasher
2.6 • Running More Than One Machine in a Program
2.7 • Driving a Seven Segment LED Display
2.8 • The Differences Between the PIC 12F1822 and the 16F1823
2.9 • Interrupts and State Diagrams
Chapter 3 • Macros, Subroutines and Bank Switching
3.1 • Introduction
3.2 • Create Your Own Instruction
3.3 • Subroutines
3.4 • Bank Switching
Chapter 4 • Inputs and Outputs
4.1 • Introduction
4.2 • Serial Output to a Computer
4.3 • Serial Input from a Computer
4.4 • Analog Inputs
4.5 ● Pulse with modulated outputs
4.6 ● Digital Inputs
Chapter 5 • Project Hardware Construction
5.1 • Introduction
5.2 • Overview of the Suggested Method
5.3 • Cutting and Drilling the board
5.4 • Populating and Wiring the Board
5.5 • The Circuit Board Test Program
Chapter 6 • Binary Arithmetic
6.1 • Introduction
6.2 • Binary Addition of Unsigned Numbers
6.3 • Binary Subtraction of unsigned integers
6.4 • Binary Subtraction with Negative Results
6.5 • Negative numbers in binary
6.6 • Binary Multiplication
6.7 • Binary Division
Chapter 7 • Digital Voltmeter Project
7.1 • Introduction
7.2 • The State Diagrams
7.3 • Scaling the Raw Analog Value
7.4 • Extracting the individual figures for the display
7.5 • Detecting No Input Voltage
7.6 • Recalibration
Chapter 8 • Troubleshooting and Planning
8.1 • Introduction
8.2 • Have an Overview of the Project
8.3 • Break Big Problems Down Into Smaller Ones
8.4 • Read Through Your Code in Detail and Add Comments
8.5 • Debugging a Running Program
8.6 • Traffic Lights
8.7 • Using Debug Macro on the Voltmeter Programmable
8.8 • A List of Things to Remember
Chapter 9 • A Comparison with C
9.1 • Introduction
9.2 • The Microchip XC8 Compiler
9.3 • Introduction to C
9.4 • Serial Communication
Chapter 10 • Further C
10.1 • Introduction
10.2 • Data Types
10.3 • More on Functions
10.4 • Integer Arithmetic
10.5 • The Voltmeter in C
10.6 • Summary of Assembly, C, and Finite State Machines
Index

Citation preview

Programming the Finite State Machine with 8-Bit PICs in Assembly and C

Enhanced 2nd Edition ;-----------Machine States. S0 MOVLW 3 SUBWF TICK_COUNTER, W BTFSS STATUS, C GOTO S0 BCF PORTA, 2 CLRF TICK_COUNTER INCF PULSE_COUNTER, F

S1

; LED on. Fast cycle. ; ; ; ; ; ; ;

C in STATUS is set when TICK_COUNTER >= to 3. Skip the next instruction if C in STATUS is found set. C in STATUS found not set. Stay in this state. Turn LED off Reset the tick counter. Add 1 to PULSE_COUNTER and put the result in PULSE_COUNTER. Continue to S1

; LED off. Fast cycle. MOVLW 10 SUBWF PULSE_COUNTER, W BTFSS STATUS, C GOTO $+3 CLRF PULSE_COUNTER GOTO S3 MOVLW 9 SUBWF TICK_COUNTER, W BTFSS STATUS, C GOTO S1 CLRF TICK_COUNTER BSF PORTA, 2 GOTO S0

; ; ; ; ; ; ; ; ;

C in STATUS is set when PULSE_COUNTER >= to 10. Skip the next instruction if C in STATUS is found set. Have not reached 10 pulses jump to ticks check. 10 Pulses have been counted, reset PULSE_COUNTER. Transition to S3. Load W with 9 for ticks check. C in STATUS is set when TICK_COUNTER >= to 9. Skip the next instruction if C in STATUS is found set. C in STATUS found set, 9 ticks counted.

; Turn LED on.

S2 MOVLW 31 SUBWF TICK_COUNTER, W BTFSS STATUS, C GOTO S2 BCF PORTA, 2 CLRF TICK_COUNTER INCF PULSE_COUNTER, F

S3

; ; ; ; ; ; ;

C in STATUS is set when TICK_COUNTER >= to 3. Skip the next instruction if C in STATUS is found set. C in STATUS found not set. Turn LED off Reset the tick counter. Add 1 to PULSE_COUNTER and put the result in PULSE_COUNTER. Continue to S3

; LED on. Slow cycle. MOVLW 3 SUBWF PULSE_COUNTER, W BTFSS STATUS, C GOTO $+3 CLRF PULSE_COUNTER GOTO S1 MOVLW 91 SUBWF TICK_COUNTER, W BTFSS STATUS, C GOTO S3 CLRF TICK_COUNTER BSF PORTA, 2 GOTO S2

; ; ; ; ;

C in STATUS is set when PULSE_COUNTER >= to 3. Skip the next instruction if C in STATUS is found set. ave not reached 3 pulses jump to ticks check. Reset PULSE_COUNTER. Transition to S1.

; C in STATUS is set when TICK_COUNTER >= to 9. ; Skip the next instruction if C in STATUS is found set. ; C in STATUS found set. ; Turn LED on.

Andrew Pratt LEARN DESIGN SHARE

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 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

Programming the Finite State Machine with 8-Bit PICs in Assembly and C

● Andrew Pratt

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 2020 First published in the United Kingdom 2020



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



ISBN: 978-1-907920-92-9

Prepress production: DMC ¦ [email protected] 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 • Preface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9

Chapter 1 • Getting Started . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 1.1 • Introduction. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 1.2 • Practical Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 1.2.1 • Choice of Operating System . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 1.2.2 • Machine Language and Assembly Source Files . . . . . . . . . . . . . . . . . . . . 11 1.2.3 • Using the Applications on Microsoft Windows . . . . . . . . . . . . . . . . . . . . . 13 1.2.4 ● Installing the Applications on Linux . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 1.2.5 • The FTDI Lead and Testing the Programming Chain. . . . . . . . . . . . . . . . . 15 1.3 • Some Fundamentals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 1.3.1 • Bits and Bytes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 1.3.2 • The Hexadecimal Numbering System . . . . . . . . . . . . . . . . . . . . . . . . . . 20 1.3.3 • Boolean Logic . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 1.3.4 • Bitwise logic . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 1.3.5 • PIC Architecture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 1.3.6 • Data Memory Organization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 1.3.7 • An Assembly Program Snippet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 1.4 • Program Memory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 1.5 • Hello World . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 1.5.1 • Some Key Points . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 1.5.2 • Assembling the Program. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 Chapter 2 • The Assembly Program as a Finite State Machine . . . . . . . . . . . . . . . . . 32 2.1 2.2 2.3 2.4 2.5 2.6 2.7 2.8 2.9

• • • • • • • • •

Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . A State Machine Framework for Assembly Language. . . . . . Timer0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Interrupts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . A More Complicated LED Flasher . . . . . . . . . . . . . . . . . . Running More Than One Machine in a Program . . . . . . . . . Driving a Seven Segment LED Display . . . . . . . . . . . . . . . The Differences Between the PIC 12F1822 and the 16F1823 Interrupts and State Diagrams . . . . . . . . . . . . . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

32 32 33 35 41 43 45 51 52

Chapter 3 • Macros, Subroutines and Bank Switching . . . . . . . . . . . . . . . . . . . . . . . 53 3.1 • Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 3.2 • Create Your Own Instruction. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54

●5

Programming the Finite State Machine with 8-Bit PICs in Assembly and C 3.2.1 • Use an Include File . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 3.2.2 • More Macros. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56 3.2.3 • Conditional Assembly. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60 3.3 • Subroutines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 3.3.1 • An Example of a Subroutine . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 3.3.2 • Return Address and the Stack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63 3.3.3 • Calculating the Delay . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63 3.3.4 • Calling Subroutines from Subroutines . . . . . . . . . . . . . . . . . . . . . . . . . . 64 3.4 • Bank Switching . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65 Chapter 4 • Inputs and Outputs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 4.1 • Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 4.2 • Serial Output to a Computer. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 4.2.1 • TTL Level Serial Communications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 4.2.2 • Configuring the EUSART to Transmit a Byte . . . . . . . . . . . . . . . . . . . . . . 68 4.2.3 • Serial Output Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 4.3 • Serial Input from a Computer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 4.3.1 • Configuring the EUSART to Receive Bytes . . . . . . . . . . . . . . . . . . . . . . . 73 4.3.2 • Interrupt Service Routine . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74 4.3.3 • Serial Input Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 4.4 • Analog Inputs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76 4.4.1 ● Setting ADCON0 and ADCON1. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78 4.4.2 ● Circuit and State Diagram . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79 4.5 ● Pulse with modulated outputs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81 4.5.1 ● LED with Pulsing Brightness . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82 4.6 ● Digital Inputs. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85 4.6.1 ● Counting Input Pulses From a Switch . . . . . . . . . . . . . . . . . . . . . . . . . . 85 4.6.2 ● First method eliminating the effect of switch bounce . . . . . . . . . . . . . . . . 88 4.6.3 ● A Better Method of Eliminating the Effect of Switch Bounce . . . . . . . . . . . 90 Chapter 5 • Project Hardware Construction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93 5.1 5.2 5.3 5.4 5.5

• • • • •

Introduction . . . . . . . . . . . . . . . Overview of the Suggested Method Cutting and Drilling the board . . . . Populating and Wiring the Board . The Circuit Board Test Program . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

93 93 94 96 97

5.5.1 • Analog Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99

●6

● Table Of Contents Chapter 6 • Binary Arithmetic . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103 6.1 • Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103 6.2 • Binary Addition of Unsigned Numbers . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103 6.2.1 • Adding Two Eight Bit Positive Integers . . . . . . . . . . . . . . . . . . . . . . . . 104 6.2.2 • Serial Read Program Command Line Arguments . . . . . . . . . . . . . . . . . . 107 6.2.3 • Adding Two Sixteen Bit Positive Numbers . . . . . . . . . . . . . . . . . . . . . . 108 6.3 6.4 6.5 6.6

• • • •

Binary Subtraction of unsigned integers . Binary Subtraction with Negative Results Negative numbers in binary . . . . . . . . . Binary Multiplication . . . . . . . . . . . . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

111 113 114 116

6.7 • Binary Division . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119 Chapter 7 • Digital Voltmeter Project. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126 7.1 7.2 7.3 7.4 7.5 7.6

• • • • • •

Introduction . . . . . . . . . . . . . . . The State Diagrams . . . . . . . . . . Scaling the Raw Analog Value . . . . Extracting the individual figures for Detecting No Input Voltage . . . . . Recalibration . . . . . . . . . . . . . . .

........ ........ ........ the display ........ ........

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

126 126 133 135 135 135

Chapter 8 • Troubleshooting and Planning . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136 8.1 • Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136 8.2 • Have an Overview of the Project . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136 8.2.1 • State Diagrams and Flow Charts . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136 8.3 8.4 8.5 8.6

• • • •

Break Big Problems Down Into Smaller Ones. . . . . . . Read Through Your Code in Detail and Add Comments Debugging a Running Program . . . . . . . . . . . . . . . . Traffic Lights . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

137 137 137 138

8.6.1 • A Circuit Diagram . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140 8.6.2 • Separate Different Problems . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140 8.6.3 • Producing the Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141 8.7 • Using Debug Macro on the Voltmeter Programmable. . . . . . . . . . . . . . . . . . . 148 8.8 • A List of Things to Remember . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150 Chapter 9 • A Comparison with C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151 9.1 • Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151 9.2 • The Microchip XC8 Compiler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151 9.2.1 • XC8 for Microsoft Windows . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151 9.2.2 • XC8 for 32 Bit Debian-Based Distributions . . . . . . . . . . . . . . . . . . . . . . 151 9.2.3 • XC8 for 64 Bit Debian-Based Distributions . . . . . . . . . . . . . . . . . . . . . . 152 9.2.4 • XC8 for 64-Bit Fedora . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152 9.3 • Introduction to C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153

●7

Programming the Finite State Machine with 8-Bit PICs in Assembly and C 9.3.1 • Hello World in C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154 9.3.2 • Using the XC8 Compiler in Microsoft Windows . . . . . . . . . . . . . . . . . . . 156 9.3.3 • Using the XC8 Compiler in Linux . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157 9.3.4 • Emitted Code Assembly vs C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157 9.3.5 • Interrupts in C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159 9.3.6 • The __delay() functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161 9.3.7 • Extending the If Statement. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162 9.3.8 • The Switch Statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163 9.3.9 • An Experiment to Measure Code Speed . . . . . . . . . . . . . . . . . . . . . . . 167 9.4 • Serial Communication . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168 9.4.1 • Serial Byte Transmission . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168 9.4.2 • Serial Byte Reception. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169 Chapter 10 • Further C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171 10.1 10.2 10.3 10.4

• • • •

Introduction. . . . . Data Types . . . . . More on Functions. Integer Arithmetic.

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

171 171 172 173

10.4.1 • Transmitting a Four-Byte Integer . . . . . . . . . . . . . . . . . . . . . . . . . . . 173 10.4.2 • The "for" loop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175 10.5 • The Voltmeter in C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179 10.5.1 • Turning Bits On and Off Using Bit Type . . . . . . . . . . . . . . . . . . . . . . . 179 10.5.2 • Turning Bits On and Off Using Bitwise Operators . . . . . . . . . . . . . . . . 183 10.5.3 • Turning Bits On and Off Using Bitfields . . . . . . . . . . . . . . . . . . . . . . . 183 10.6 • Summary of Assembly, C, and Finite State Machines . . . . . . . . . . . . . . . . . 184 • Index. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185

●8

● Preface



Preface

This practical guide is aimed at electronics students and hobbyists. It is intended to be a valuable aid in writing programs using Finite State Machines (FSMs) in assembly language using 8-bit PIC microcontrollers. The last two chapters introduce the use of the C programming language and make a direct comparison with development in Assembly. An FSM is a way of writing a program to make it easier to produce and modify. The machine is abstract in that it is just the structure of the program. This abstract machine can be represented by drawing a diagram on paper. The diagram is independent of the programming language used. The FSM chart gives a complete description of what the program does. It can then be implemented as source code. The book should appeal to those with an interest in the combination of electronics and software and have an interest in how things work. The book will describe writing code for two particular microcontrollers: The 12F1822 and 16F1823. Both are mid-range and inexpensive. To read and write the programs to and from the PICs, all that is required is an FTDI TTL level USB lead (TTL-232R-5V-WE) in addition to two programs that are both available for free download as executable files and source code from Elektor. Microsoft Windows or Linux can be used. The PIC programs are written in assembly language. This goes against the conventional wisdom of using a higher-level language such as C. One reason for this is that assembly is a good way of learning what is happening at the lowest level. This is important as microcontroller programming requires an understanding of the chip. Another reason for using the finite state machine approach is that it makes assembly programs surprisingly easy to follow. One of the main obstacles in the way of getting started with embedded programming is the installation and learning of new software tools. The emphasis of this book is on making things straightforward with as little complication as possible. Therefore you can concentrate on understanding the code. Real projects aren’t just about coding: our software has to do something real. As a consequence, a chapter deals with a method of circuit board construction. All coding is done in a text editor of your choosing. The command line is used for running programs. If you are a Windows user, you might look at this as old fashioned. This is actually an efficient way of doing things: simple scripts for repetitive tasks save lots of mouse clicks. The last two chapters give an introduction to programming in C using the XC8 compiler. Again this is done using a text editor and the command line. The intention has been to achieve results using an inexpensive microcontroller with simple command line tools. Much emphasis is placed on using Microchip’s datasheet as this is the best place to get correct detailed information.

●9

Programming the Finite State Machine with 8-Bit PICs in Assembly and C

Chapter 1



Getting Started

1.1 • Introduction

Don’t be put off by the technical-sounding words ‘Finite State Machine’ (FSM). The principle of how they work is really easy to understand. Firstly, the machine is not an actual machine that you have to build. It is merely a structure your program has. The idea is that the program is broken down into a set number of states. When the program is in one of these states, it can respond to a set of inputs or events resulting in the program jumping to a different state. The outputs from the program depend only on the state (Moore machine) or on the state and the inputs (Mealy machine). Let’s start with the basic ‘Hello World’ microcontroller program to flash an LED as an example of a state machine with two states, LED ON and OFF.

Figure 1-1 A two-state machine

Figure 1-1 displays what the program does and how to write it. What the program does is: After power up, the initial conditions are the LED is ON and the timer is reset and started. After the timer expires, the LED turns OFF and the timer is reset and started. After the timer expires again, the LED turns ON and the timer is reset and started. This continues until the microcontroller is powered down In this example, there is only one event that triggers a change of state: This being the expiry of the timer. The effect of the expiry depends on the current state - it might jump to the LED ON state or the LED OFF state. The fact that an input or event is only used by the current state helps to make a more complicated program robust and easier to follow. This example is a Mealy machine, where outputs or actions are determined by the current state and inputs. On the state diagrams, the transitions between states are shown by arrows. Alongside these are the input conditions that trigger transitions and the resulting outputs. In a Moore machine, the outputs depend only on the current state of the machine. The outputs would be shown on the diagram inside the states. This book is specifically about practical programs. I will not try to adhere to strict definitions. As for how

● 10

Chapter 1 ● Getting Started

to write the program, you have already done the important bit by planning. All that has to be done now is to implement the diagram as code and this will be less prone to error as you can concentrate on local detail. The book will mostly use the Mealy type machine. Inputs causing a transition will be separated by a forward slash from the outputs. This will be written next to the transition between two states. With regards to the FSM, inputs and outputs (I/O) are not restricted to electrical I/O. They also include other types of change. An input could be the timer timing out or an output could be the writing of a value to a register in the CPU. FSMs can be written in just about any programming language. 1.2 • Practical Implementation

Microcontroller projects do not need to be expensive. The two PICs that are going to be used throughout the book are the 12F1822 and 16F1823 variants. Both are cheap midrange devices, costing less than one pound in the UK at the time of writing. They are available in the dual in-line package (DIP) form that is easy to solder or can be plugged into a holder. The programs will be written in a text editor and assembled with the gpasm open-source assembler. To load your programs into the PIC, you could use a programmer such as PICKIT 3, but there is no need to buy one. Two programs have been written for this book that can read and write using an FTDI USB lead TTL-232R-5V-WE. This same lead can also be used for reading and writing data from PICs in later projects. 1.2.1 • Choice of Operating System

Microsoft Windows or Linux can be used for assembling and loading programs to the PICs. There are two archive files available by way of a free download from the Elektor website: One is for Windows and the other for Linux. These files contain all the source files for the examples in the book and applications mentioned above. As described in the preface, the command line method is used throughout the book for both Linux and Windows. The use of the terminal with typed commands might seem like a throwback to the last century for those who remember using DOS. It is, however, a very easy way of doing things, especially for repetitive tasks like running assembler programs or loading the PIC with its program. Tasks can be simplified with scripts to save typing and recent commands can be recalled using the up and down keys. If you are using Linux, typing history will give a numbered list of the previous commands you have used. They can be run again by using the exclamation mark followed by the number. Auto-completion allows you to use the tab key to list possible files. You never have to type the complete name. If you are happy writing source code for an electronic device using text files then using the command line should be the logical choice. Graphic User Interfaces (GUIs) have their advantages. However, if you are doing something repetitive, a lot of mouse clicking or even using keyboard shortcuts is tedious compared with running a script or recalling a previous command and its options. 1.2.2 • Machine Language and Assembly Source Files

The PIC microcontroller has to be programmed by loading the machine code into its flash memory. The word ‘programmed’ in the last sentence means transferring machine code from a file on a computer to the PIC as opposed to writing the code. The machine code is

● 11

Programming the Finite State Machine with 8-Bit PICs in Assembly and C

the raw byte values that it processes when running. This machine code is first assembled into a file called a hex on your computer. It gets its name from the Intel Hex Format which is a file standard for this purpose. The hex file is then written to the PIC using a combination of a program on your computer and a hardware interface called a programmer. When referring to machine code, this is not the finite state machine but the processor itself being referred to as a machine. It is possible to write your program by typing a hex file directly into a text editor. Manually assembling machine instructions is too time-consuming to be considered a realistic task. To make it easier, code is written in assembly language where short acronyms for machine instructions are used to produce more readable code. The file that you type this way is the source code and usually has the file suffix .asm. This file is then read by the assembler application and written to the hex file as raw machine code. It is generally accepted that it is better to write your programs in a higher-level language. If you want to get things done quicker and with less chance of error, then this is absolutely true. C language was invented to get away from the problems of programming in assembly language. This book is about finite state machines implemented on simpler PICs. To write code for a microcontroller, you have to understand what is going on at the level where software meets hardware. Where C makes coding faster and easier, it also makes things more complicated. You don’t, however, see this extra complication: it’s taken care of by the compiler and linker. All of this gets in the way of understanding what your code is doing. For the PICs used in this book, there are 49 available instructions. The ones commonly used are quickly learnable. The beauty of assembly language is that you can see what’s going on. The code you write is exactly the machine code that goes into the PIC. Once the source code file is finished, it is converted to machine language by the assembler application: in our case the program gpasm. The assembler reads your source code and outputs the hex file. The hex file is then read by another application that writes to the PIC using some form of hardware interface. Figure 1-2 below depicts this programming chain.

● 12

Chapter 1 ● Getting Started

Figure 1-2 Programming Chain

You can if you prefer, use MPLABX and PICKET 3 or 4 from Microchip. You may already have these and be familiar with them. The next two paragraphs describe an alternative using the applications provided in the download. 1.2.3 • Using the Applications on Microsoft Windows

First, you need to download and install gputils for Windows from here: https://sourceforge.net/projects/gputils/files/gputils-win32/ The version used at the time of writing was 1.4.0. Although not the latest version, it was chosen to be the same as used by the current Debian and Ubuntu. You can use the latest version if like. You will need the FTDI driver for your USB interface lead, and the installation of the file FTD2XX.DLL. The best way to do this is to download the executable setup for Windows from: http://www.ftdichip.com/Drivers/D2XX.htm At the time of writing, it is named CDM2128_Setup.zip. Inside the zip file is CDM2128_ Setup.exe that will install the driver and the required DLL file. Install it before plugging in the lead. You need to unzip the file from the Elektor website into a convenient directory on your computer. Please make sure that you download the correct file. The Linux version won’t work on Windows and vice versa. It’s not just executables that are not compatible. Text files have different line endings: Linux files have a new line character while Windows files

● 13

Programming the Finite State Machine with 8-Bit PICs in Assembly and C

have a carriage return and new line character at the end of each line. The file unzips several directories. The working directory contains a batch file called terminal_here. Clicking on this will open a virtual terminal. If you type dir and press return, the contents of the directory will be displayed. Initially, the files in the working directory are executable. These executables will be run from the command line. This only involves typing a couple of words and pressing return. Later in this chapter, instructions will be given on how to use these applications. For reading and writing PIC source files, you can use Windows’ text editor, notepad, but any text editor will do, including notepad++. 1.2.4 ● Installing the Applications on Linux

The archive file for Linux is a tar file. This can be extracted by typing on the command line tar -xvf .tar. There are many variants of Linux. I won’t try to cover the instructions in detail for all of them. I will give instructions for two: Debian and Fedora. The details for Debian are also applicable to Ubuntu and other Debian derivatives. The assembler program gpasm is part of the GNU PIC Utilities (gputils) package. This can be installed on Debian based Linux by opening a terminal window and typing: sudo apt-get install gputils. Of course, you have to be connected to the Internet. On Fedora, the command is sudo dnf install gputils.

Figure 1-3a Installing gputils on Debian (Ubuntu etc)

For Fedora:

Figure 1-3b Installing gputils on Fedora

To load the hex file contents into the PIC microcontroller and read back the loaded bytes, the two programs that have been written especially for this book are called program_ writer_xx and program_reader_xx, where xx is 32 or 64. Both versions are provided: one for 32-bit and one for 64-bit operating systems. These programs need to have the package libftdi1-dev installed to work. This can be installed on Debian by opening a terminal window and typing sudo apt-get install libftdi1-dev .

Figure 1-4a Installing libftdi1-dev on Debian

● 14

Chapter 1 ● Getting Started

For Fedora:

Figure 1-4b Installing libftdi on Fedora

These two applications have only been tested with these two PICs. Figure 1-5 shows the directory trees for the Linux and Windows downloads.

Figure 1-5 Download directory tree for Linux and Windows

Once you have all the software installed, the next thing is to try it out using the hardware. 1.2.5 • The FTDI Lead and Testing the Programming Chain

All that is required is an FTDI TTL-232R-5V-WE USB wire end lead. They are readily available from suppliers such as Farnell, RS Components, and Digi-Key. 1k resistors to pins 4, 6, and 7 are recommended for protection. The one connected to pin 5 is to limit the current flowing through the LED.

● 15

Programming the Finite State Machine with 8-Bit PICs in Assembly and C

Figure 1-6 In-Circuit Programming Diagram

The above circuit can quickly be connected to a plugin breadboard: see Figure 1-9. The LED is not part of the programming circuit. It is there for the test program to demonstrate that it is all correctly working. The test program test.hex is included in the Chap01_progs folder of the archive file. It makes the LED flash on for a quarter of a second and off for threequarters of a second. You need to copy it to the working directory to use it. If you are using Windows, click on the terminal_here batch file in the working directory to open the terminal window. Type in the following and press return. See the upper pane in Figure 1-7. program_writer.exe test.hex

If you are using Linux, open a virtual terminal and navigate to the working directory. Type the following and press return. See the lower pane in Figure 1-7. sudo ./program_writer_64

test.hex

Note that in Linux you must prefix the executable file with ./ .This is the path to the present directory. The sudo command will be required to acquire the privilege of opening the USB port. Once the code is loaded into the PIC, the LED should start flashing. The code can be verified by reading it back using the program program_reader: refer Figure 1-8. Only a few lines of the output from these programs is shown in the screen dumps. Take care to correctly connect the wires and get the LED the correct way round. The PIC has pin 1 marked with a dimple on the top surface.

● 16

Chapter 1 ● Getting Started

Figure 1-7 Writing the Hex File to the PIC: Windows and Linux 64 bit

Figure 1-8 Reading Back PIC Code: Windows and Linux

● 17

Programming the Finite State Machine with 8-Bit PICs in Assembly and C

Figure 1-9 Plugin breadboard

1.3 • Some Fundamentals

Assuming that everything has so far worked, you can move on in the next chapter to writing a first PIC program with confidence that it can be written to the controller. The rest of this chapter is of benefit to those who are not familiar with PIC programming in assembly language. All that is required to write the code is a text editor. You should also have open a copy of the datasheet for the 12F1822 and 16F1823 which can be downloaded free as a PDF file from Microchip. The name of the document is DS40001413E. To get you on your way, I will present a program that flashes an LED at about 5Hz. This is not the same program as test. hex that was used to prove the programming chain above. Writing code for a microcontroller requires knowledge of the inner workings of a device. The controller is part of an electronic circuit. The code is an extension of the circuit that you are building. If you browse through the datasheet, you’ll see that there are more than four hundred pages. Trying to read a document like this can be overwhelming as it has not been written as a teaching aid and is packed with information. We will start slowly with just enough detail to start-off. We will add more as the projects get more advanced. Once you start to become familiar with the layout of this manual, it is quite easy to find what you want to know as you progress. I’m assuming that all of this is new to you and will try to explain all you need to know to understand this first program. 1.3.1 • Bits and Bytes

Let’s start with the bit. The word bit is the contraction of binary digit. A bit is a basic unit of

● 18

Chapter 1 ● Getting Started

information: it has only two states commonly referred to as 0 or 1. Physically these states can be represented by say a voltage of 0V or +5V. Any physical quantity that can have two distinct values could be used. Now if two bits are placed next to each other, there are now four permutations that bits can be in, 00, 01, 10, 11. If three bits are used, there are eight permutations, 000, 001, 010, 011, 100, 101, 110, 111. Whenever a bit is added to the group, the number of permutations double. A byte is normally a group of eight bits and these have 256 permutations. Each arrangement of bits can be used to represent an integer. To start with we’ll look at positive integers and zero. Negative integers can also be expressed but that’s for later. Bits that are 1 are sometimes referred to as being on, and off when they are 0. Each bit in a byte not only has a value of 0 or 1 but has a position in the byte. We give weight to the positions so that the bit on the right end is worth 1 when it is on and 0 when it is off. The next position has a weight of 2 so that bit is worth 2 when it is on and 0 when it is off. Continuing this binary weighting to all eight bits in a byte, all the values from 0 to 255 can be allocated to bit states.

0

128

64

32

16

8

4

2

1

0

0

0

0

0

0

0

0

1

0

0

0

0

0

0

0

1

2

0

0

0

0

0

0

1

0

3

0

0

0

0

0

0

1

1

4

0

0

0

0

0

1

0

0

5

0

0

0

0

0

1

0

1

6

0

0

0

0

0

1

1

0

7

0

0

0

0

0

1

1

1

8

0

0

0

0

1

0

0

0

9

0

0

0

0

1

0

0

1

10

0

0

0

0

1

0

1

0

11

0

0

0

0

1

0

1

1

12

0

0

0

0

1

1

0

0

13

0

0

0

0

1

1

0

1

14

0

0

0

0

1

1

1

0

15

0

0

0

0

1

1

1

1

16

0

0

0

1

0

0

0

0

-

-

-

-

-

-

-

-

-

255

1

1

1

1

1

1

1

1

Table 1-1 Binary Weighting

● 19

Programming the Finite State Machine with 8-Bit PICs in Assembly and C

For example, eleven is expressed by the bits with the weights of 8, 2 and 1 being set on. This is no different to the way we count in tens. The number 364 is four lots of units plus six lots of tens plus three lots of hundreds. Each place is weighted by a factor of ten. If we add 6 to 364 it becomes 370. The units position has overflowed and the value of ten has been carried and added to the tens position. The same happens with binary addition. Here are three separate examples:

Figure 1-10 Additions with Carry

When an addition results in the overflow of a byte, a carry is generated that adds to the next higher byte. If the first byte counts up to 255, the next will count the number of 256s and so on with the next higher byte counting the number of 65536s and so on. 1.3.2 • The Hexadecimal Numbering System

In everyday life, we use the decimal numbering system. Microcontrollers and computers operate using the binary system as described above using bits and bytes. Converting the expression of a value backward and forwards to a binary bit pattern is not very convenient. There is an alternative to the decimal system that uses 16 characters instead of the ten. What makes this convenient is that a group of four bits has sixteen different permutations. To have 16 characters representing the values from 0 - 15, the letters A to F are used for 10, 11, 12, 13, 14, and 15. This means that a byte’s value can be represented by two hexadecimal digits. Figure 1-11 shows an example of the decimal number 44 which is 00101100 as eight bits displayed as hexadecimal 0x2C. Two ways of indicating that a number is being expressed in hexadecimal notation, are to prefix it with ‘0x’ or suffix it with ‘h’. Hexadecimal offers a way of writing binary numbers in a way that is easier than long strings of ones and noughts in a way that can be converted to individual bits easily.

● 20

Chapter 1 ● Getting Started

Figure 1-11 Example of a Hexadecimal Number (0x2C)

1.3.3 • Boolean Logic

In programming, bits are often operated on by logic operations to give a binary result either true or false. The operations used are AND, OR, NOT, and XOR. A good way to see how this works is the truth table. Figure 1-12 shows these. In the tables, only two input bits called the operands are shown for the AND, OR, and XOR operators. The NOT simply inverts the value of one bit. XOR stands for exclusive OR.

Figure 1-12 Boolean Logic Truth Tables

1.3.4 • Bitwise logic

If we carry out boolean logic with corresponding bits in two or more words, the result will be another word. Here is an example to demonstrate this. Taking two words with the values 0x1234 and 0xABCD and then performing the bitwise AND operation the result is 0x0204. This sort of operation is frequently used in low-level programming.

● 21

Programming the Finite State Machine with 8-Bit PICs in Assembly and C

Figure 1-13 Bitwise Logic

1.3.5 • PIC Architecture

Inside the PIC is a central processing unit connected to a set of peripheral devices, memory and inputs and outputs. Refer to pages 9 and 16 of the datasheet. You get a lot for your money! Throughout the book and in comments to the source code files, I will refer to pages to supplement information. These are the pages in the Microchip DS40001413E datasheet unless stated otherwise. There are three areas of memory: Program Memory Non-volatile: it retains information when the power is off. It is flash memory. This is where your program (the hex file) is loaded to. Data Memory Volatile: it does not retain information when the power is off. It is a random access memory (RAM), made up of banks of eight-bit registers also referred to as files in this context. Electrically Erasable Programmable Read-Only Memory (EEPROM) Non-volatile: this can be used to store data when the power is off. This is called the Harvard architecture where the program and the data are in separate memory areas. 1.3.6 • Data Memory Organization

Data memory is made up of 32 banks. Each bank has 128 locations although not every location is implemented. Each location is one byte or eight bits wide. Some of the implemented memory can be used to store variables. Other locations are registers that have predefined use and are called special function registers. Pages 22 to 25 of the datasheet shows a map of data memory. Of these special registers, 12 are core registers. These are repeated in every bank in the first 12 locations. I’ll start by mentioning only three of these registers:

● 22

Chapter 1 ● Getting Started

W Working Register: temporary storage used by logic and arithmetic operations. STATUS A register containing the status of the arithmetic, logic unit of the CPU and reset status. BSR Bank Select Register: This register has to be set to the bank that you want to access. Bank selection is a feature of this type of PIC. It comes about because of the format of machine instructions. They are 14 bits wide. For some instructions, 7 bits are used to point to the address of a register. Note that the word file is used, meaning register. Here is an example of one of the instructions, BCF (bit clear file). This sets a particular bit in a file to zero. If you wanted to turn bit 5 in the PORTA register off, the assembly code would be BCF PORTA, 5.

0

1

0

Operation code

0

b

b

b

Bit in the file e.g bit 5 = 1 0 1

f

f

f

f

f

f

f

File in the bank e.g PORTA is 0x0c in bank 0. 0x0C = 0 0 0 1 1 0 0

Table 1-2 Bit Clear File (BCF) Instruction

There are only 7 bits in the instruction for the file address, which limits the range to 128 one bank. If you are in the wrong bank, for example, bank 1 instead of bank 0, and think you are going to access PORTA you would get TRISA - not what you wanted. The bank is set in BSR. There is an MOVLB instruction (move literal to bank select register) that is used to load BSR directly. To use it you have to remember or look up the bank number for each file you want to access. To make it easier, there is an assembly directive provided called BANKSEL that generates the correct code. For example, BANKSEL PORTA will generate MOVLB 0. Please note it is the gpasm assembler’s job to generate the instructions like the one in Table 1-2 using the operation code (opcode) and the operands from each line of code. Page 308 onwards describes all the detail you need to manually assemble the instructions. You would however, then have to format the Intel hex file. I will revisit the topic of bank selection in Chapter 3. In assembly programming, the term ‘move from one location to another’ means to copy the value to a destination. It does not delete the value from the source location. 1.3.7 • An Assembly Program Snippet

From an electronic point of view, a microcontroller is a programmable integrated circuit or chip. It has several electrical connections or pins that can be used as inputs or outputs. The PIC can be programmed to perform many different functions. To get an idea, I will describe part of a program to make a 12F1822 into an AND gate. This is a trivial use of a PIC and only part of the code will be shown to demonstrate what you can expect when assembly

● 23

Programming the Finite State Machine with 8-Bit PICs in Assembly and C

programming.

Figure 1-14 PIC 12F1822 as an AND gate

When you write a program for a computer, there is an operating system such as Windows, Linux, BSD or macOS that deals with hardware interfacing by way of a kernel. The PIC has no operating system. You have to deal with the details of hardware such as configuring inputs and outputs, oscillator frequency or perhaps configuration of the serial port (if you want to communicate with a PC). Figure 1-14 shows how the PIC would appear if programmed to be an AND gate. You might want to argue that what I have drawn is a ‘NOT OR’ gate. As the switches pull the inputs down to 0V when they are closed, from the point of view of the voltages on the inputs, they all have to be high for the output to go high. Therefore, it is an AND gate. Each input has an internal high-value resistor that pulls it up to 5V when the pin is open circuit. This is called a weak pull-up and is configured in the code. This saves on external components as all that is needed is a switch to pull the voltage on the pin-down. Something that I should mention is that the programming method used by my programs, program_writer, and program_reader use low voltage programming with the FTDI lead as opposed to high voltage programming that would require pin 5 MCLR/Vpp/ RA3 to have between 8 and 9V applied to it to enter the programming mode. Low voltage programming can be performed with just a 5V supply and special train of bits applied to the ICPDAT pin to enter programming mode. The downside of this is pin 4 can only be used as MCLR (not master clear reset). This means that 0V applied to this pin resets the PIC. Pin 4 must therefore always have 5V applied to it. The weak pull-up does this. It is enabled by default in low voltage programming mode. This means we have lost the use of RA3 as an input pin. It is never an output anyway. Don’t concern yourself with all this detail about programming methods for now. If you want more information about the details of this then

● 24

Chapter 1 ● Getting Started

take a look at page 1, note 2 of the datasheet on in circuit DS41390C programming. This datasheet explains everything about the in circuit programming of these PICs. One last point: the line drawn over MCLR means ‘NOT MCLR’. If the voltage is 5V, it is not resetting the PIC. It resets on 0V. Now for a look at some assembly language code. The code once written to the PIC is a list of machine instructions. Generally, one instruction is executed for every four clock cycles. Clock cycles are produced by the oscillator. Everything in this book will use the internal oscillator. No external components are required for the oscillator. Each instruction is located at a memory address in the flash memory of the PIC. As the program runs, the current instruction is pointed to by the program counter. This counter increments by one as each instruction is executed. Some of the instructions can cause the program counter to jump forwards or backward. These jumps can be decided on by the outcome of logical or mathematical operations. If you have programmed in another language such as C/C++, Java, Python, etc, you will be familiar with the if...else statement used frequently to execute one piece of code or another based on whether an expression is true or false. Consider this example in pseudo-code. if

bit Z in the STATUS register is high then turn the output on;

else turn the output off; end_if;

Figure 1-15 shows how to implement this in PIC assembly language. Before the code that is shown here, a subtraction calculation is carried out. If the result is zero, bit Z in STATUS will be set on. BTFSS (bit test file skip on set) tests a bit in a file. If the bit is high, the next instruction will be skipped. If low, the next instruction will be executed. In typing your assembly program, the opcode mnemonics are indented. The words on the extreme left are labels that you invent to mark places in the code. These labels can be used as the destination for GOTO opcode.

● 25

Programming the Finite State Machine with 8-Bit PICs in Assembly and C

Figure 1-15 Part of an Assembly Language Program Implementing ‘if ...else’

PORTA is the register holding the state of the pins for port A. In this example bit 5 was configured as an output. Therefore, writing to bit 5 sets the electrical state of the pin. The BTFSS STATUS, Z decides whether to skip the next instruction. If Z is high, the program jumps to the line BSF PORTA, 5 (bit set file) and turns on bit 5 in PORTA. If Z is low the next instruction GOTO ELSE is executed. The program then jumps to BCF PORTA, 5 (bit clear file) turning off bit 5. 1.4 • Program Memory

The program is held in flash memory. Each location is 14 bits wide and holds a machine instruction. The next instruction to be executed is held in the program counter that is 15 bits wide making it capable of addressing 32k locations. The 12F1822 and 16F1823 PICS have only 2048 locations implemented. The rate at which instructions are executed is determined by oscillator frequency. It takes four clock cycles for every instruction cycle. Program memory is divided up into pages that are 2048 locations long. The program counter PC that points to the current location in the program is 15 bits wide. It can point to a range of 0 to 32767 or 32568 locations. The PC is loaded from two registers: PCL and PCLATH. This occurs under five circumstances that are described on page 38 of the datasheet. PCL is one of the special function registers and appears in all banks. It provides the lower 8 bits of PC, PCL (standing for Program Counter Lower). PCLATH (standing for Program Counter Latch Higher) provides the higher 7 bits. If you are using a PIC that has more than 2048 program memory locations, you have to be aware that your program will cross page boundaries. Fortunately, we don’t have to worry

● 26

Chapter 1 ● Getting Started

about this with these two PICs as all of our program will be on page 0. This can be seen in the diagram on page 18 of the datasheet. 1.5 • Hello World

Here is the obligatory ‘Hello World’ program which flashes an LED. It is the same as the state machine described in the introduction to the chapter except that in state 1 the LED is off instead of on, and the timer is a counter that resets by rolling over from 255 to 0. ; prog_01_01.asm ‘Hello World’ LIST P=12f1822 #INCLUDE __CONFIG 0X8007, (_FOSC_INTOSC & _WDTE_OFF & _PWRTE_OFF & _CP_OFF & _BOREN_OFF & _IESO_OFF & _FCMEN_OFF) __CONFIG 0X8008, (_LVP_ON) CBLOCK 0x70 COUNTER ENDC

; Declares a register as the variable COUNTER at location 0x70.

ORG 0x00

; ; ; ;

MOVLB 1 BCF TRISA, 2 MOVLW 0x10 MOVWF OSCCON MOVLB 0 STATE_0 DECFSZ COUNTER, F

GOTO STATE_0 BSF PORTA, 2 STATE_1 DECFSZ COUNTER, F GOTO STATE_1 BCF PORTA, 2 GOTO STATE_0

Directive to the assembler to start assembling the code from location 0x00. Change to bank 1 for TRISA and OSCCON. Sets RA2 (pin 5) to be an output.

; Configures the oscillator to run at 31.25kH (page 65). ; Change to bank 0 for PORTA.

; ; ; ; ; ;

COUNTER is at memory location 0x70 its value is decremented by 1 and the result is stored back in the file COUNTER if the result is zero the program skips a line otherwise the next line is executed. If the value of COUNTER is not zero this line is executed. The program skips to here if the COUNTER is zero and turns the output on.

; This loop works the same way.

; After turning the output off the program jumps back to the off loop.

END

Program 1-1 Hello World

1.5.1 • Some Key Points



• •

On any line where there is a semi-colon, the rest of the line is a comment and is ignored by the assembler. The first line; Program 1-1 Hello World, is just to identify the program to the human reader. The second line has the word LIST followed by p= 12f1822, this tells the assembler program what type of processor is being used. The #include line tells the assembler to read this file. The file is one of the header files containing numerical definitions of abbreviations used in the assembly code. If you want to read this file, it is in the /usr/share/gputils/headers/ folder if you are using Linux, and C:\Program Files(x86)\gputils\header\ if you are using Windows. It can be read with a text editor. Don’t alter it! The lines beginning with __CONFIG are extremely important. They set the bits in the two memory locations known as configuration words. For the PICs, they are at addresses 0x8007 and 0x8008 in flash

● 27

Programming the Finite State Machine with 8-Bit PICs in Assembly and C

memory. These bits determine things like which oscillator to use. This time it is the internal oscillator, looking in the include file _FOSC_INTOSC has the hexadecimal value 0x3FFC. This sort of thing might make no sense at all. If you follow through this one example you will see how logical it is. Expressed as binary 0x3FFC is 11 1111 1111 1100. The configuration word 1 uses 14 bits as described in the datasheet on page 46. The three least significant bits are the oscillator selection bits. For the internal oscillator, this is 100. The remaining bits in this value for the whole word are set to default values. The interesting part is that the other attributes in this line of code for things to be configured say ‘watch dog timer off’ _WDTE_OFF = 0x3FE7 = 0011 1111 1110 0111) are bitwise ANDed to give the value for the configuration word. Figure 1-16 shows this for our program.

Figure 1-16 Bitwise AND Determines the Config words







The CBLOCK 0x70 line is a directive to the assembler to mark the files starting at 0x70 with the names given in the following lines up to the instruction ENDC. Here there is only one name given. That is COUNTER that will be used as a variable to hold a value. ORG 0x00 tells the assembler to start assembling machine code at location 0x00 in flash memory. MOVLB means move a literal value to the bank select register, in this case, the value 1. In paragraph 1.3.6 above is an explanation of banked addressing. Banked addressing is something you need to keep track of at all times when programming the PICs in assembly. BCF TRISA, 2 means clear a bit in a file, that is set it to 0. The bit is bit 2 and the file is TRISA. TRISA is described on page 117 of the datasheet. It is the file that determines the data direction for the pins of PORTA. If the bit is set to 0, the pin is an output and can be driven high or low by the program. If the bit is set to 1, the pin is an input and

● 28

Chapter 1 ● Getting Started

• •

• •



• •

has a high input impedance. This is referred to as being a tri-state pin. Bit 2 is the bit that sets the direction for pin 5 of the 12F1822 PIC. Page 3 of the datasheet shows a diagram. MOVLW 0x10 moves the literal value expressed here in binary to the working register of the CPU. MOVF OSCCON moves the value in the working register to the oscillator control register to give a frequency of 31.25 kHz. Whenever a value is to be passed to a file, it has to be passed via the working register. The hexadecimal value 0X10 is B’00010000’ in binary. This means that the IRCF bits that set the oscillator will be 0010. Refer to page 65 of the datasheet. There is now a label STATE_0 that is used to mark a position in the program. DECFSZ means decrement a file and skip the next instruction if the result is zero. The file being COUNTER. Now COUNTER can have any value from 0 to 255 (0xFF). When it reaches 0, the next decrement will bring it back to 255. If the result is NOT zero, the next instruction is GOTO STATE_0, so the program loops round and round until zero is reached. If the result is zero, the next instruction is skipped and the BSF PORTA, 2 instruction is executed. This causes the PORTA’s bin controlled by bit 2 (physical pin 5) to be turned on. BSF means to set a bit in a file. The program then moves on to STATE_1. STATE_1 works the same way as STATE_0 except it ends with BCF PORTA, 2 (bit clear) to turn the LED off, and then jumps back to STATE_0. Lastly, the code must end with END.

1.5.2 • Assembling the Program

To assemble the program, carry out the following: Copy the source file prog_01_01.asm from the chap01_source_xxxx (xxxx = Windows or Linux) directory to the working directory. Windows: Click on the terminal_here.bat batch file in the working terminal to open a terminal window. Type the following on the command line and press return: gpasm.exe prog_01_01.asm Linux: Open a virtual terminal and navigate to the working directory. Type the following on the command line and press return: ./gpasm prog_01_01.asm This will generate a few files, one of which is the hex file prog_01_01.hex. To write this to the PIC, type the following on the command line and press return: Windows: program_writer.exe prog_01_01.hex

● 29

Programming the Finite State Machine with 8-Bit PICs in Assembly and C

Linux 32 bit: sudo ./program_writer_32 prog_01_01.hex Linux 64 bit: sudo ./program_writer_64 prog_01_01.hex The gpasm assembler produces a file prog_01_01.hex that is written to the PIC using the program ‘program_writer’. You will, of course, have to connect everything up as described earlier in the chapter. The message saying to check the bank bits is not an error. It is just a reminder to ensure you are in the right bank. Note how the value written to flash memory address 0x8007 agrees with Fig 1-16 above. Figure 1-17 shows this being done on a 64-bit Linux system. For the differences between Windows and Linux, please study Figures 1-7 and 1-8.

Figure 1-17 Assembling prog_01_01.asm and Writing prog_01_01.hex to the PIC

I hope you are getting a feel for how assembly programming works. It is extremely verbose where every action is visible in the code. This makes it all the more interesting as, unlike high-level languages where things seem to happen by magic, here you can trace what is happening at a hardware level. This means that you have to be on top of a lot more detail and is more work. The C programming language was developed fifty years ago to overcome the problems of writing code in assembly. Modern software projects would take an enormous amount of human work without languages like C, C++, Java, Python, etc. Please don’t get the idea that I’m trying to persuade you that assembly is better by using it here. It is generally agreed that you should code in a higher-level language to save time if nothing else. Modern compilers can outperform most humans at code optimisation. This book focuses on midrange PICs where programming in assembly is a practical proposition, and where executable code will be smaller and faster than that produced by the free version of the XC8 compiler. To be fair to Microchip, the free version states that smaller and faster code can be produced by the paid-for version. There is also good reason for coding in assembly. If you are doing this as a hobby or to learn then it is more fun. If you take a look at any code written in C for a PIC, like these you will see that there is still a need to manually select bits in registers when configuring oscillator, inputs, outputs, etc.

● 30

Chapter 1 ● Getting Started

The rest of the book is mostly about using finite state machine methods to structure your programs. This helps to keep things in some kind of order. Along the way, more features will be introduced. The last two chapters will be an introduction to programming in C where you can make a comparison with programs already written in assembly.

● 31

Programming the Finite State Machine with 8-Bit PICs in Assembly and C

Chapter 2 • The Assembly Program as a Finite State Machine 2.1 • Introduction

This chapter demonstrates a format for laying out an FSM in assembly that will be used throughout the rest of the book. The first example will be a two-state machine that will have a structure that can be scaled to have more states and even have more than one state machine in the program allowing a form of program threading. The PIC is not just a processor but has other features such as three timers that can run independently of the code. There is also the ability to interrupt and divert the program using events from within and from outside the PIC. As was mentioned in the first chapter I advise having the datasheet for these PICs to hand while programming, it is DS40001413E and is available for free download from the Microchip website. Throughout the book, I will refer to pages in the datasheet where relevant. Where a page number is quoted it refers to the Microchip datasheet. 2.2 • A State Machine Framework for Assembly Language

Figure 2-1 shows the two-state machine to flash an LED on and off. We will use this as the first step to develop a standard framework for an FSM in assembly language. With a Moore machine, the outputs are dependent only on the current state, whereas with a Mealy machine the outputs are dependent on the state and the value of the inputs. Most of the FSMs in this book will be based on the Mealy format, in the state diagram here the input is the expiry of the timer, this causes the transition, and the outputs are the resetting of the timer and the switching of the LED. The inputs and outputs are written on the transition arrow separated by a forward slash.

Figure 2-1 Example FSM

The operation of this machine has already been explained in Chapter 1. In the ‘Hello World’ program in Chapter 1, the timer was just a counter that triggered the transition when it rolled over. What needs to be elaborated on is the practical implementation of the timer that decides when to change state in this program where we want to measure the time more precisely. This is another counter that counts the number of times that a fast timer repeatedly expires and then starts timing again. This fast timer can be thought of as generating ticks. It will tick at approximately 122 times per second. Although the

● 32

Chapter 2 ● The Assembly Program as a Finite State Machine

state diagram shows the logic of the sequencing, in any practical program there will be some support code off to the side that does things like configuring the PIC’s peripherals or counting clock cycles, etc. The next two sections will describe how this fast timer is configured and used. Also how its ticks are counted to provide the inputs to the machine. 2.3 • Timer0

These PICs have three onboard timers that run independently of the program. In the Hello World program in the last chapter, we created a delay by decrementing a file value until it reached zero. This is a bad way of creating longer delays as the CPU is doing nothing other than counting instruction cycles. The three timers are timer0, timer1, and timer2. We will start by looking at timer0. The first problem is how do timers keep track of time? They work as counters counting clock cycles, but because each timer is a separate hardware entity, they do this concurrently with the program. Because the timers are counting clock cycles, we must take into account the frequency of the oscillator being used. We will be using the internal oscillator where a range of frequencies from 31 kHz to 32 MHz can be selected. Timer0 is an eight-bit timer/counter that can be used as a timer or a counter. Here I’ll describe how to configure it as a timer. At the heart of the module is the TMR0 register that is in bank 0 of the data memory. See page 22 of the datasheet. When the timer is running it will count up until its value is 255 and then overflow or roll over to 0. The time it takes to overflow will depend on the value loaded into it, to begin with, the clock frequency, and the configuration of a Prescaler that can divide the incoming frequency. The full block diagram can be seen on page 162 of the datasheet. Figure 2-2 is a simplified diagram of our application.

Figure 2-2 Simplified Block Diagram of timer0

● 33

Programming the Finite State Machine with 8-Bit PICs in Assembly and C

Starting from the left-hand side of the diagram, the tapered block is a multiplexer that can choose a signal from more than one source. The simplified diagram shows a dotted line coming from circuitry associated with an input pin. We are not using this source here. The signal we are using is the one that carries the clock frequency divided by four. To select this, the TMROCS bit in the OPTION_REG must be set to 0. The next choice is: do we want the frequency of cycles to be fed straight through to be counted or do we use the Prescaler? The Prescaler can divide the frequency by 2 through to 256 according to the bits PS in the OPTION_REG. The PS means the three bits numbered 0,1 and 2 referred to as PS, see page 164 in the datasheet when this notation is used the numbers do not refer to the bit numbers in the register but to the bit numbers in that group. Here is an example that will be used in the next program. We are going to set the internal oscillator to run at 32 MHz. The internal oscillator has already been selected in the configuration word 1. The frequency is set using bits IRCF actually bits 6, 5, 4 and 3 in the register OSCCON (page 65). These are set to 1110. The code to do this is: MOVLW

0x70

;Move (copy) 0x70 to W (working register)

MOVWF

OSSCON

;Move (copy) W to OSCCON

In the datasheet, it says 8 or 32 MHz for this selection of bits depending on whether the phase-locked loop is enabled. As the phase lock loop is defaulted to be enabled in configuration word 1, the frequency will be 32 MHz. The tables below are a handy way of working out what you need to move to registers during configurations. You can do this on a bit of paper: just write the bits 7-0 in a vertical column and by reading from the datasheet, add the bit settings then convert the two groups of bits 7-4 and 3-0 to two hexadecimal digits. Once converted to hex, they are less likely to be typed into your code incorrectly. From Table 2-1, OSCCON will be loaded with 0x70.

OSCCON

7

0

7

0

6

1

5

1

4

1

3

0

2

0

1

0

0

0

Left at default PLL is set by PLLEN in configuration word 1

32 Mhz

Unimplemented Clock determined by configuration word 1

Table 2-1 Example Bit Settings for OSCCON

To use the Prescaler, PSA in the OPTION_REG must be set 0. The Prescaler ratio is to be 1:256, so bits PS in OPTION_REG must be set to 111. We don’t need the weak pullups to be enabled, so by leaving the other bits that don’t affect this project at their default,

● 34

Chapter 2 ● The Assembly Program as a Finite State Machine

we get the bits for OPTION_REG to be 11010111. This is summarized in Table 2-2. OPTIONS_REG

D

7

7

1

Left at default.

6

1

Left at default.

5

0

Timer0 clock select (TMR0CS) Fosc/4.

4

1

Left at default.

3

0

Prescaler assignment PSA to TMR0.

2

1

1

1

0

Prescaler set to 1:256.

1 Table 2-2 Example Bit Settings for OPTIONS_REG

The code to set OPTION_REG is going to be: MOVLW 0xD7

;Move (copy) 0xD7 to W (working register)

MOVWF OPTION_REG

;Move (copy) W to OPTION_REG

We now have a frequency of 32Mhz divided initially by 4 then by 256 by the Prescaler presenting a frequency of 31.25kHz to be counted by TMR0. 32000000/(4 x 256) = 31250.0 If TMR0 starts from 0, it will overflow after it has counted 256 cycles as it is an 8-bit register. It will take 256/31250 seconds to overflow: that is 0.008192 seconds or 8.192ms. Stated another way, it will overflow at a frequency of 1/0.008192Hz or 122.07Hz. The next paragraph will show how to catch the points in time when the overflow of TMR0 occurs. 2.4 • Interrupts

Suppose you are working on something but you have arranged to meet someone at eleven o’clock. How can you be sure you are not going to miss the appointment? One way is to keep checking your watch to see what the time is. This is called polling where the value of something is repetitively read. This method is often used in software but can be a wasteful use of resources if the polling has to be frequent. Another way of ensuring that you get to your appointment on time is to set an alarm on your watch to tell you it’s time to go. When you hear the alarm, you stop work, go to your appointment and then return to work. The PIC can be programmed to respond to events that can interrupt the program, do something else, and then return to what it was doing. Many events can be used as interrupt sources: a timer overflow being one of them. There are a few things that have to be done to use an interrupt. The first example here will

● 35

Programming the Finite State Machine with 8-Bit PICs in Assembly and C

demonstrate how to use timer0 to cause a routine to be carried out every time it overflows and then return the program to where it was. To use interrupts, the global interrupt enable bit GIE must be set in register INTCON (page 86). Additionally, the peripheral interrupt enable bit PEIE and timer0 overflow interrupt enable bit TMROIE must be set, again in register INTCON When an interrupt occurs, the program completes the instruction it is currently executing. The GIE bit is cleared to prevent another interrupt occurring. The program counter is pushed on to the hardware stack. A block in the core, 15 bits wide that has 16 locations is used for storing program counter values. The value being stored is the return address where the program must continue from after the interrupt is over. Important registers such as W, STATUS, and some others are temporarily stored in shadow registers in bank 31 to be recovered later. The program counter now jumps to 0x004. This special address is called the interrupt vector. This happens automatically. If you look at page 39 of the datasheet, there is an explanation of the stack. The code starting at 0x004 is called the interrupt service routine and you have to write it. The first thing to do is to determine the cause of the interrupt. In our first example, we are interested in TMR0 overflowing. When TMR0 overflows, the timer0 interrupt flag TMR0I in INTCON gets set and this is used to establish the cause of the interrupt. At this point, we can execute any code we want. TMROIF can now be cleared by our code as the interrupt has been caught. Next, the instruction return from interrupt RETFIE is used. This returns the program to where it was by popping the return address off the stack to the program counter. It restores the W, STATUS, and other registers (they are listed on page 85) from the shadow registers and sets GEI again so that other interrupts can be caught. If another interrupt occurs while an interrupt is being serviced, it is not forgotten. Its interrupt flag is still set and will cause another interrupt when the GEI is set again. Figure 2-3 shows the program flow for an interrupt.

● 36

Chapter 2 ● The Assembly Program as a Finite State Machine

Figure 2-3 Program Flow for an Interrupt (timer0)

The first code we need is to configure INTCON. All of the bits are 0 by default. Bit 7 GEI, global interrupt enable has to be set. Bit 6 PEIE peripheral interrupt enable and bit 5 TMR0 overflow interrupt enable TMR0IE have to be set. This gives a hexadecimal value of 0xE0. MOVLW 0xE0

; See Table 2-3 in the book.

MOVWF INTCON

; See page 86.

If TMR0 overflows, the automatic events described above will happen and the program will be at 0x0004. We have to write the interrupt service routine to handle this. We can cheat on this first simple example because there is only one interrupt enabled. There is no need to check to see what the interrupt is, so the interrupt service routine is simpler. What we are going to do is increment the counter by one every time the interrupt occurs. This counter counts the ticks. Here is the simplified ISR for this example. ORG 0X04

; Interrupt vector.

BCF INTCON, TMR0IF

; Clear TMR0 overflow interrupt flag, INTCON is in all banks.

INCF TICK_COUNTER, F

; Increment by one TICK_COUNTER, result back in the file.

RETFIE

; Return from interrupt.

● 37

Programming the Finite State Machine with 8-Bit PICs in Assembly and C INTCON

E

0

7

1

Global Interrupt Enable Bit

6

1

Peripheral Interrupt Enable Bit

5

1

Timer0 Overflow Interrupt Enable Bit

4

0

INT External Interrupt Enable Bit

3

0

Interrupt on Change Enable Bit

2

0

Timer0 Overflow Interrupt Flag Bit

1

0

INT External Interrupt Flag Bit

0

0

Interrupt-on-Change Interrupt Flag Bit

Table 2-3 Example Bit Settings for INTCON Register

The INTCON register is available in all banks (page 22). This counter is in the block of 16 memory locations accessible for all banks, 0x070 – 0x07F (page 21). We now have an interrupt occurring every time TMR0 overflows from 255 back to 0, 122 times per second. This is way too fast for our flashing LED as it would appear to be on all the time, but dimmer. This is where the counter comes in. The code is going to have two states: one for LED on and one for off. The counter TICK_COUNTER is going to be incremented by the interrupt code. The counter is going be checked to see if it is greater than or equal to 31 whilst in the on state. If it is, the state will be changed. This means that the on-time will be 31/122 of a second: approximately 0.25s. When in the off state, the counter will be checked to see if it has reached 91. This will give a delay of 91/122 of a second: approximately 0.75s. Here are the lines of code for the on-state. The counter is reset as being one of the outputs or actions during the transition. S0

; LED on.

MOVLW 31 SUBWF TICK_COUNTER, W BTFSS STATUS, C

; C in STATUS is set when counter_on >==31. ; Skip next instruction if C in STATUS is found set

GOTO S0

; C in status found not set.

CLRF TICK_COUNTER

; Reset the counter.

BCF PORTA, 2

; Turn the LED off.

With gpasm, the default number base is hexadecimal. To express decimal numbers greater then 9 such as ninety-one, you have to put D’91’. I prefer to add the line ‘RADIX DEC’ to the code so that the base is now 10. Conventional notation for numbers can now be used. From now on, numbers will be expressed as follows. 11 Means eleven in decimal. 0x11 Means seventeen in hexadecimal. B’11’ Means three in binary. Confusing number bases between decimal and hexadecimal is another particular cause of bugs.

● 38

Chapter 2 ● The Assembly Program as a Finite State Machine

The SUBWF instruction can be a bit confusing and needs to be carefully considered. The meaning of the instruction is to subtract what is in the working register W from what is in the file. The file can be any register. The value in W is subtracted from COUNTER_ON and if the result is negative, the carry flag in STATUS set to 0. If the result is zero or positive, the carry flag is set to 1. The result will be positive or zero if COUNTER_ON has reached or exceeded the value in W, in this case, 31. Page 320 in the datasheet describes this. Figure 2-4 below shows this visually.

Figure 2-4 Explanation of SUBWF

The code for the off block works in the same way but the timing is different and the LED is turned on. Here is the complete program:

● 39

Programming the Finite State Machine with 8-Bit PICs in Assembly and C ; prog_02_01.asm ; Page numbers refer to the data sheet DS40001413E. ; Tables refer to the book. LIST P=12f1822 #INCLUDE RADIX DEC

; Default numbers to base 10

__CONFIG MEN_OFF) __CONFIG

0X8007, (_FOSC_INTOSC & _WDTE_OFF & _PWRTE_OFF & _CP_OFF & _BOREN_OFF & _IESO_OFF & _FC0X8008, (_LVP_ON)

CBLOCK 0x70 TICK_COUNTER ENDC ORG 0X00 GOTO START ORG 0X04 BCF INTCON, TMR0IF INCF TICK_COUNTER, F RETFIE START MOVLB 1 BCF TRISA, 2 MOVLW 0x70 MOVWF OSCCON MOVLW 0xD7 MOVWF OPTION_REG MOVLW 0xE0 MOVWF INTCON MOVLB 0 BSF PORTA, 2 ;----------Machine States S0 MOVLW 31 SUBWF TICK_COUNTER, W BTFSS STATUS, C GOTO S0 CLRF TICK_COUNTER BCF PORTA, 2 S1

; ; ; ;

Interrupt vector. Clear TMR0 overflow interrupt flag, INTCON is in all banks. Increment by one TICK_COUNTER, result back in the file. Return from interrupt.

; TRISA and OSCCON, OPTION_REG. ; Make RA2 an output. ; Page 65 32 MHz. See Table 2-1 in the book. ; ; ; ; ;

Page 164 in the data sheet and Table 2-2 in the book. See Table 2-3 in the book. See page 86. Stay in bank 0 to access PORTA for the rest of the program. Initialise LED to on;

; LED on. ; ; ; ; ;

C in STATUS is set when counter_on >==31. Skip next instruction if C in STATUS is found set. C in status found not set. Reset the counter. Turn the LED off.

; LED off. MOVLW 91 SUBWF TICK_COUNTER, W BTFSS STATUS, C GOTO S1 CLRF TICK_COUNTER BSF PORTA, 2 GOTO S0

; C in STATUS is set when counter_on greater than or equal to 91. ; Skip the next instruction if C in STATUS is found set. ; C in STATUS found not set. ; Reset the counter. ; Turn the LED on.

END

Program 2-1 First State Machine

To assemble and load the program into the PIC, start by copying the program into the working directory, and then carry out these steps: Windows: gpasm.exe prog_02_01.asm program_writer.exe prog_02_01.hex Linux 32 bit: gpasm prog_02_01.asm sudo ./program_writer_32 prog_02_01.hex

● 40

Chapter 2 ● The Assembly Program as a Finite State Machine

Linux 64 bit: gpasm prog_02_01.asm sudo ./program_writer_64 prog_02_01.hex Please note when using Linux, the executable gpasm has been installed and is on the PATH. Therefore it can be found by the operating system so you only have to type its name. Other executables such as program_writer_64, that are not installed on the PATH have to be prefixed with the path to the file, if the file is in the present directory, the path is ./ so this has to be put in front of all the local executables that are in the download archive file. This does not apply to Microsoft Windows where you only need the name of an executable in the present directory or one that is on the PATH. 2.5 • A More Complicated LED Flasher

One of the criticisms of assembly language programs is that they can difficult to read. This can be true even if you have only just created them! Having a standard layout for your program makes this is a lot easier. Also, the finite state diagram gives an overview of the whole operation. The assembly code is in separate blocks that are the states. You will find that by referring to the state diagram, following the assembly code is straightforward. As an example of a more complicated program, Figure 2-5 is the state diagram for the same LED but now it flashes on and off with the same intervals as before but only for 3 flash cycles. It then flashes 10 times faster for 10 cycles before going back to the slower speed. Note there are four states with six transitions. There are off and on states for fast flashing and off and on states for slow flashing. The whole diagram can be divided into four quadrants with two dividing lines: one between fast and slow and one between on and off.

Figure 2-5 State diagram for LED Flashing Cycles of Cycles

● 41

Programming the Finite State Machine with 8-Bit PICs in Assembly and C

Fig 2-6 shows the oscilloscope trace for the voltage across the LED.

Figure 2-6 OscilloscopeTrace for the Cycle of Cycles

The program is shown below. By referring to the state diagram, you should be able to understand what is happening. There is one new thing to explain: the use of the $ sign. This means the address of the current line, so instead of jumping to a label, we can jump so many lines. Writing GOTO $+3 will jump forwards 3 lines for example. ; prog_02_02.asm ; Page numbers refer to the data sheet DS40001413E. ; Tables refer to the book. LIST P=12f1822 #INCLUDE RADIX DEC

; Default numbers are to base 10.

__CONFIG 0X8007, (_FOSC_INTOSC & _WDTE_OFF & _PWRTE_OFF & _CP_OFF & _BOREN_OFF & _IESO_OFF & _FCMEN_ OFF ) __CONFIG 0X8008, (_LVP_ON)

CBLOCK 0x70 TICK_COUNTER PULSE_COUNTER ENDC

; In commom RAM, accessible in all banks.

ORG 0X00 GOTO START ORG 0X04 BCF INTCON, TMR0IF INCF TICK_COUNTER, F RETFIE START MOVLB 1 BCF TRISA, 2 MOVLW 0x70 MOVWF OSCCON MOVLW 0xD7 MOVWF OPTION_REG MOVLW 0xE0 MOVWF INTCON MOVLB 0

● 42

; ; ; ;

Interrupt vector. Clear the tmr0 overflow interrupt flag. INTCON is in all banks. Increment by one counter_off. Return from interrupt

; To access TRISA, OSCCON, OPTION_REG. ; Configure PORTA bit 2 (chip pin 5) as an output ; Page 65 32MHz. See Table 2-1 in the book. ; Page 164 in the data sheet and Table 2-2 in the book. ; Page 86 in the data sheet and Table 2-3 in the book. ; To access PORTA for the rest of the program.

Chapter 2 ● The Assembly Program as a Finite State Machine BSF PORTA, 2 ;-----------Machine States. S0 MOVLW 3 SUBWF TICK_COUNTER, W BTFSS STATUS, C GOTO S0 BCF PORTA, 2 CLRF TICK_COUNTER INCF PULSE_COUNTER, F

; Inialise LED on;

S1

; LED off. Fast cycle. MOVLW 10 SUBWF PULSE_COUNTER, W BTFSS STATUS, C GOTO $+3 CLRF PULSE_COUNTER GOTO S3 MOVLW 9 SUBWF TICK_COUNTER, W BTFSS STATUS, C GOTO S1 CLRF TICK_COUNTER BSF PORTA, 2 GOTO S0

; LED on. Fast cycle. ; ; ; ; ; ; ;

; ; ; ; ; ; ; ; ;

C in STATUS is set when TICK_COUNTER >= to 3. Skip the next instruction if C in STATUS is found set. C in STATUS found not set. Stay in this state. Turn LED off Reset the tick counter. Add 1 to PULSE_COUNTER and put the result in PULSE_COUNTER. Continue to S1

C in STATUS is set when PULSE_COUNTER >= to 10. Skip the next instruction if C in STATUS is found set. Have not reached 10 pulses jump to ticks check. 10 Pulses have been counted, reset PULSE_COUNTER. Transition to S3. Load W with 9 for ticks check. C in STATUS is set when TICK_COUNTER >= to 9. Skip the next instruction if C in STATUS is found set. C in STATUS found set, 9 ticks counted.

; Turn LED on.

S2 MOVLW 31 SUBWF TICK_COUNTER, W BTFSS STATUS, C GOTO S2 BCF PORTA, 2 CLRF TICK_COUNTER INCF PULSE_COUNTER, F

S3

; ; ; ; ; ; ;

C in STATUS is set when TICK_COUNTER >= to 3. Skip the next instruction if C in STATUS is found set. C in STATUS found not set. Turn LED off Reset the tick counter. Add 1 to PULSE_COUNTER and put the result in PULSE_COUNTER. Continue to S3

; LED on. Slow cycle. MOVLW 3 SUBWF PULSE_COUNTER, W BTFSS STATUS, C GOTO $+3 CLRF PULSE_COUNTER GOTO S1 MOVLW 91 SUBWF TICK_COUNTER, W BTFSS STATUS, C GOTO S3 CLRF TICK_COUNTER BSF PORTA, 2 GOTO S2

; ; ; ; ;

C in STATUS is set when PULSE_COUNTER >= to 3. Skip the next instruction if C in STATUS is found set. ave not reached 3 pulses jump to ticks check. Reset PULSE_COUNTER. Transition to S1.

; C in STATUS is set when TICK_COUNTER >= to 9. ; Skip the next instruction if C in STATUS is found set. ; C in STATUS found set. ; Turn LED on.

END

Program 2-2 Cycles of Cycles 2.6 • Running More Than One Machine in a Program

The two FSM programs have so far had a small block of code for each state and have run that code over and over until some input event causes a jump to another state. I will now suggest a way of running more than one machine at once in a program: This is like doing two things at once by performing part of one task then part of another task then back to the first and so on. This is time multiplexing, the CPU in the microcontroller can only carry out one instruction at a time so if tasks are to be carried out together they must share the processor’s time. Unlike timers and other peripheral parts of the PIC that are separate hardware entities that can truly run concurrently, if there is more than one task in the program, they only appear to run concurrently. They are running as threads, with each thread sharing the processor’s time. To achieve this multi-threading, the first machine

● 43

Programming the Finite State Machine with 8-Bit PICs in Assembly and C

will execute the block of code for its current state and then the block of code for the other machine’s current state and then back to the first machine. These different machines are independent. They just happen to use the same CPU by time-sharing. They can also share memory to access each other’s data. In the examples so far, the state was defined by being in a certain block of code now the state is going to be defined by the value stored in a register. Let the register that holds the state of machine 0 be STATE_M0 and for machine 1 be STATE_M1. Figure 2-7 below shows how this program flow will be.

Figure 2-7 Example of Program Flow Between Two machines in the Same Program

To control this switching between states, we can use the instruction BRW. The BRW instruction is a branch with W meaning that the program counter will increase by the value held in W. In other words, you can do a calculated jump. After a block of state code is executed, the program will jump back to the switch for the next machine where the BRW instruction will go to the next line if the state value is 0 or skip a line if it is 1 and skip two lines if the value is 2. The program flow is not trapped in the state blocks but routes round via the switch code. To run two machines as threads, there will be two state variables: STATE_M0, STATE_M1, and two switches. Below is a

● 44

Chapter 2 ● The Assembly Program as a Finite State Machine

simplified code snippet of this fictitious program, with machine 0 having two states and machine 1 having 3 states. SWITCH_M0 MOVFW STATE_M0

; Moves the value of STATE_M0 to W.

BRW

; The program will jump from here depending on W.

GOTO M0_S0

; The jump will be to this line if W = 0.

GOTO M0_S1

; The jump will be to this line if W = 1.

SWITCH_M1 MOVFW STATE_M0

; Moves the value of STATE_M0 to W.

BRW

; The program will jump from here depending on W.

GOTO M1_S0

; The jump will be to this line if W = 0.

GOTO M1_S1

; The jump will be to this line if W = 1.

GOTO M1_S2

; The jump will be to this line if W = 1.

M0_S0 -------------

; code to decide if a change of state is needed if so change STATE_M0.

GOTO SWITCH_M1

; Go to the other machine’s switch.

M0_S1 -------------

; code to decide if a change of state is needed

GOTO SWITCH_M1

; Go to the other machine’s switch.

if so change STATE_M0.

M1_S0 -------------

; code to decide if a change of state is needed

GOTO SWITCH_M0

; Go to the other machine’s switch.

if so change STATE_M1.

M1_S1 -------------

; code to decide if a change of state is needed

GOTO SWITCH_M0

; Go to the other machine’s switch.

if so change STATE_M1.

M1_S2 -------------

; code to decide if a change of state is needed

GOTO SWITCH_M0

; Go to the other machine’s switch.

if so change STATE_M1.

Notice how the program flow alternates between machines when leaving a block of state code. 2.7 • Driving a Seven Segment LED Display

For a practical demonstration of the use of the switches to handle two machines in the same program, this example will have a flashing LED and a 7 segment display that counts the number of flashes. There is a choice of a liquid crystal or an LED display. LED displays are easier as they can be driven with direct current, while liquid crystal displays require

● 45

Programming the Finite State Machine with 8-Bit PICs in Assembly and C

alternating voltage. LEDs have the disadvantage that they require several milliamps per segment. There are lots of 7-segment LED displays available. I am using a single digit display. Figure 2-8 shows the physical connections for one typical pattern. The displays come in two distinct arrangements, common cathode, and common anode. With common cathodes, the cathodes must be connected to 0V. With common anode, anodes must be connected to +5V. The segment connections are then connected to the output pins of the PIC. The logic of the output code has to be designed to suit the polarity of the display. It would be easier if you use a display with this pinout arrangement, but if not you will have to work out your own wiring. The LA-601VB common anode display that can be supplied by RS Components is similar to the one I used. Farnell has a similar one (part number SA5211SRWA).

Figure 2-8 Connections for Typical 7-Segment LED Displays

As there are a total of 8 LEDs to drive, the 16F1823 PIC is used. This has an extra IO port, PORTC. Let’s start with Figure 2-9 - the circuit diagram for connections to the PIC.

● 46

Chapter 2 ● The Assembly Program as a Finite State Machine

Figure 2-9 Connections for the PIC 16F1823 and 7-Segment LED Display

The next part of the design process is to draw the finite state machine diagram (see Figure 2-10). Machine M1 is the display driver. On power-on, the LED is initialised to on, and then machine M0 starts in state 0. After 0.25s, the timer expires and machine 0 transitions to state 1. The timer is reset, the LED turned off, the pulse counter is incremented by 1 and a signal called UPDATE is set to high. This UPDATE signal is used by machine 1. While in state 1, when the timer expires at 0.75s, the machine transitions to state 0 and the timer resets and its LED turned on. This continues indefinitely. When in state 1, if the pulse counter has reached 10, there is a transition to the same state. This is to reset the pulse counter to zero. Meanwhile, machine M1 is running independently but time sharing the CPU. Machine M1 starts in state 0 and the display will be blank. When the UPDATE signal goes high, the machine transitions to state 2 and resets the UPDATE signal to low. Depending on the value of the variable N, the machine transitions back to state 1, driving on and off the necessary segments of the display. The machine will remain in state 0 waiting for another update signal. The output details shown for machine M1 are for a common anode display. The comments in the code show the alternatives for a common cathode display.

● 47

Programming the Finite State Machine with 8-Bit PICs in Assembly and C

Figure 2-10 State Diagram for Program 2-3

Nearly everything in program prog_02_03.asm has been already explained, except for one thing you have to watch out for: The READ MODIFY WRITE problem that can happen when using the BSF and BCF instructions. Up to now, we have only been setting one output pin by setting the bit value on PORTA. This works if you are only working with one output pin on a port but something strange happens when trying to alter an output shortly after altering a different pin. To understand the problem, we have to look at basic electrical theory regarding charging and discharging capacitors, along with how the PIC handles changing the value of one pin at a time. When the PIC writes to outputs it can only write to a complete byte or eight bits, so to change one bit it has to read the state of the pins, modify the one it wants to alter, and then write back to the outputs. The problem is that say PORTA bit 5 (physical pin 2) has been changed from 0 to 1, and then in the next instruction, you want to change the value on PORTA bit 4 (physical pin 3). The PIC reads the values on the PORTA pins - you might expect the value on bit 5 to be high as you have just set it, but NO because in the real world the electrical circuit could be capacitive and it will take time for the voltage to rise, resulting in the value of bit 5 reading as low. When the outputs are written back to port, bit 5 (pin 2) gets set back to low. This is an example of where testing in a simulator can let you down. Generally, when using BCF and BSF you should write to the latch registers for the ports (LATA and LATC). If however, you are writing to the whole port as a register, for example, using MOVWF PORTA, then this is safe.

● 48

Chapter 2 ● The Assembly Program as a Finite State Machine ; prog_02_03.asm ; Page numbers refer to the data sheet DS40001413E. ; Tables refer to the book. LIST P=16f1823 #INCLUDE #INCLUDE RADIX DEC __CONFIG MEN_OFF ) __CONFIG

; Default numbers are to base 10.

0X8007, (_FOSC_INTOSC & _WDTE_OFF & _PWRTE_OFF & _CP_OFF & _BOREN_OFF & _IESO_OFF & _FC0X8008, (_LVP_ON)

CBLOCK 0x70 TICK_COUNTER STATE_M0 STATE_M1 PULSE_COUNTER UPDATE ENDC ORG 0X00 GOTO START ORG 0X04 BCF INTCON, TMR0IF INCF TICK_COUNTER, F RETFIE START MOVLB 1 BCF TRISA, 2 BCF TRISA, 4 CLRF TRISC MOVLW 0x70 MOVWF OSCCON MOVLW 0xD7 MOVWF OPTION_REG MOVLW 0xE0 MOVWF INTCON CLRF STATE_M0 CLRF STATE_M1 CLRF UPDATE CLRF PULSE_COUNTER CLRF TICK_COUNTER MOVLB 2 BSF LATA, 2 MOVLW 0xFF MOVWF PORTC BSF LATA, 4

; Block of variables

; ; ; ;

Interrupt vector. Clear the TMR0 overflow interrupt flag. Increment by one TICK_COUNTER, result in the file. Return from interrupt.

; ; ; ; ; ; ; ;

OSCCON, OPTION_REG, TRISC. Configure PORTA bit 2 as an output. Configure PORTA bit 4 as an output. Configure all of PORTC as outputs. See Table 2-1 in the book. Set to 32MHz, page 65. See Table 2-2 in the book. Page 164.

; ; ; ; ; ; ; ; ;

Page 86 in the data sheet and Table 2-3 in the book. Initialise STATE_M0 to 0. Initialise STATE_M1 to 0. Initialise UPDATE to low. Initialise PULSE_COUNTER to 0. Initialise TICK_COUNTER to 0. To access LATA and LATC for the rest of the program. Initialise LED on For common cathode 0x00. Initialise blank display.

; For common cathode use BCF. Initialise blank display.

SWITCH_M0 MOVF STATE_M0, W BRW GOTO M0_S0 GOTO M0_S1

; ; ; ;

Move STATE_0 to the working register. Program jumps by the value moved into the working register. Here if W is 0. Here if W is 1.

SWITCH_M1 MOVF STATE_M1, W BRW GOTO M1_S0 GOTO M1_S1

; ; ; ;

Move STATE_0 to the working register. Program jumps by the value moved into the working register. Here if W is 0. Here if W is 1.

M0_S0 MOVLW 31 SUBWF TICK_COUNTER, W BTFSS STATUS, C GOTO SWITCH_M1 INCF STATE_M0, F BCF LATA, 2 CLRF TICK_COUNTER INCF PULSE_COUNTER, F BSF UPDATE, 0 GOTO SWITCH_M1

; LED on. ; ; ; ; ; ; ; ;

C in STATUS is set when TICK_COUNTER_ON >= to 31. Skip the next instruction if C in STATUS is found set. C in STATUS found not set. Tansition to state 1. Turn LED off Reset TICK_COUNTER Add one to the pulse count. Set bit 0 of UPDATE

● 49

Programming the Finite State Machine with 8-Bit PICs in Assembly and C M0_S1 MOVLW 10 SUBWF PULSE_COUNTER, W BTFSS STATUS, C GOTO $+2 CLRF PULSE_COUNTER MOVLW 91 SUBWF TICK_COUNTER, W BTFSS STATUS, C GOTO SWITCH_M1 INCF STATE_M0, F CLRF TICK_COUNTER BSF LATA, 2 CLRF STATE_M0 GOTO SWITCH_M1

; LED off

M1_S0 BTFSS UPDATE, 0 GOTO SWITCH_M0 BCF UPDATE, 0 MOVLW 1 MOVWF STATE_M1 GOTO SWITCH_M0 M1_S1 MOVF BRW GOTO GOTO GOTO GOTO GOTO GOTO GOTO GOTO GOTO GOTO GOTO

; ; ; ;

Testing to see if the pulse count has reached 10. If pulse count < 10 skip clearing PULSE_COUNTER. On the state diagram this is shown as leaving and reentering the state. In practice we just stay in the state.

; ; ; ; ; ; ;

C in STATUS is set when TICK_COUNTER_OFF >= to 91. Skip the next instruction if C in STATUS is found set. C in STATUS found not set. Staying in this state. Skipped to here as time has expired. Clear TICK_COUNTER. Turn LED on. Change to state 0

; Testing UPDATE bit 0 to se if an update has been called, skip if so. ; Here because no update called for, remain in this state. ; Here beacause update called for, clear the update signal. ; Change to state 1.

PULSE_COUNTER, W N_0 N_1 N_2 N_3 N_4 N_5 N_6 N_7 N_8 N_9 N_10

; Program will jump by the value in W. ; Jumps to the appropriate output setting as required by PULSE_COUNTER. ; “” ; “” ; “” ; “” ; “” ; “” ; “” ; “” ; “” ; “”

N_0 MOVLW 0x00 MOVWF PORTC BSF LATA, 4 CLRF STATE_M1 GOTO SWITCH_M0

; For common cathode use 0x3F.

MOVLW 0x36 MOVWF PORTC BSF LATA, 4 CLRF STATE_M1 GOTO SWITCH_M0

; For common cathode 0x09.

MOVLW 0x21 MOVWF PORTC BCF LATA, 4 CLRF STATE_M1 GOTO SWITCH_M0

; For common cathode 0x1E.

MOVLW 0x24 MOVWF PORTC BCF LATA, 4 CLRF STATE_M1 GOTO SWITCH_M0

; For common cathode 0x1B.

MOVLW 0x16 MOVWF PORTC BCF LATA, 4 CLRF STATE_M1 GOTO SWITCH_M0

; For common cathode 0x29.

MOVLW 0x0C MOVWF PORTC BCF LATA, 4 CLRF STATE_M1 GOTO SWITCH_M0

; For common cathode 0x33.

; For common cathode use BCF.

N_1

; For common cathode use BCF.

N_2

; For common cathode use BSF.

N_3

; For common cathode use BSF.

N_4

; For common cathode use BSF.

N_5

● 50

; For common cathode use BSF.

Chapter 2 ● The Assembly Program as a Finite State Machine N_6 MOVLW 0x08 MOVWF PORTC BCF LATA, 4 CLRF STATE_M1 GOTO SWITCH_M0

; For common cathode 0x37.

MOVLW 0x26 MOVWF PORTC BSF LATA, 4 CLRF STATE_M1 GOTO SWITCH_M0

; For common cathode 0x19.

MOVLW 0x00 MOVWF PORTC BCF LATA, 4 CLRF STATE_M1 GOTO SWITCH_M0

; For common cathode 0x3F.

MOVLW 0x04 MOVWF PORTC BCF LATA, 4 CLRF STATE_M1 GOTO SWITCH_M0

; For common cathode 0x3B.

; For common cathode use BSF.

N_7

; For common cathode use BCF.

N_8

; For common cathode use BSF.

N_9

; For common cathode use BSF.

N_10 MOVLW 0xFF MOVWF PORTC BSF LATA, 4 CLRF STATE_M1 GOTO SWITCH_M0

; For common cathode 0x00. ; For common cathode use BCF.

END GOTO SWITCH_M0 N_6

Program 2-3 Two Machines Seven-Segment Display Counting Pulses

2.8 • The Differences Between the PIC 12F1822 and the 16F1823

The obvious difference is that the 12F1822 has 8 pins, while the 16F1823 has 14. This is because the 16F1823 has two I/O ports. Apart from this difference, programming is almost the same. You must quote the correct include file at the top of your source code and state the correct controller for your chip. Below are the relevant lines: LIST

P=12F1822

#INCLUDE LIST

P=16F1823

#INCLUDE

The advantage of a smaller chip is that it takes up less space on a circuit board and there are fewer pins to solder if you don’t need the extra port. You must consult the datasheet as there are always traps to fall into. There will be occasions when you will stare at your code trying to understand what is wrong, but the answer will be in the datasheet.

● 51

Programming the Finite State Machine with 8-Bit PICs in Assembly and C

2.9 • Interrupts and State Diagrams

I have not tried to show interrupts in any way on the state diagrams as they do not change the state of the machine. When an interrupt occurs, the main program is suspended: see paragraph 2.4 above. Therefore no change in state occurs until after the return from interrupt and then only if the interrupt has changed the input to the state machine (such as an increase in a counter changing the result of comparison).

● 52

Chapter 3 ● Macros, Subroutines and Bank Switching

Chapter 3 • Macros, Subroutines and Bank Switching 3.1 • Introduction

The circuit from Chapter 2, Fig 2-9, can be used for all of the examples in this chapter. This circuit drives the 7-segment LED display and the single LED using the 16F1823 PIC. You might by now be thinking that assembly language is just too inconvenient. It does take longer to write and can be difficult to read. Take for example Fig 2-4 of the last chapter where a comparison of two values was made to decide between two courses of action in the program. If you are used to a higher level language like C, it could be something like this: if (tick_counter < 31) { goto S0 } else { goto S1 }

Even if you have never seen this programming language before, you would understand it. In contrast, this is what we have. ;----------Machine States S0

; LED on. MOVLW 31 SUBWF TICK_COUNTER, W

; C in STATUS is set when counter_on >==31.

BTFSS STATUS, C

; Skip next instruction if C in STATUS is found set.

GOTO S0

; C in status found not set.

CLRF TICK_COUNTER

; Reset the counter.

BCF PORTA, 2

; Turn the LED off.

S1

; LED off. MOVLW 91

The MOVLW 31 line is easy to follow. It is just moving the literal value decimal 31 into the working register. The next line, SUBWF TICK_COUNTER_ON, W, means subtract the value in the W register from the register TICK_COUNTER_ON and put the result in the W register. The next line makes reading this difficult. You have to know what effect the sign of the result has on the carry flag C in the STATUS register and then work out if a skip is going to happen. Page 320 of the datasheet explains how SUBWF (subtract w from f) works. This describes what the code example means:

● 53

PProgramming the Finite State Machine with 8-Bit PICs in Assembly and C C=1

31 ≤ TICK_COUNTER_ON

C=0

31 > TICK_COUNTER_ON Table 3-1 Carry Flag Behaviour for SUBWF

If C is 0, the program executes the GOTO S0 line, because TICK_COUNTER_ON has not reached 31 yet. If C is 1 it skips this line and moves on to the code that follows. What we need is an instruction that looks more like the if...else statement that can be read more easily. While we’re on the subject, if you were wondering about goto, it is part of the C language but isn’t recommended. 3.2 • Create Your Own Instruction

The macro allows you to define your instruction that can be reused. For the problem above, the code snippet can be written as follows. Both states are shown here: S0

; LED on. MOVLW 31 IF_REG_LESS_THAN_W TICK_COUNTER

; See macro in fsm_macros.inc.

GOTO S0 CLRF TICK_COUNTER

; Reset the counter.

BCF PORTA, 2

; Turn the LED off.

S1

; LED off. MOVLW 91 IF_REG_LESS_THAN_W TICK_COUNTER

; See macro in fsm_macros.inc.

GOTO S1 CLRF TICK_COUNTER

; Reset the counter.

BSF PORTA, 2

; Turn the LED on.

GOTO S0 END

We have introduced a new instruction IF_REG_LESS_THAN_W meaning if a register value is less than the value in W, execute the next line, else skip and execute the line after. The register is presented as the argument TICK_COUNTER. Define the macro before the place in the code where it is first used. The definition is started by typing its name at the start of the line and following it with the word MACRO, then names for the arguments. There will be one argument. In this case, the register that is to be compared to the value in W. Subsequently add the code you want the macro to perform. End the macro definition with ENDM.

● 54

Chapter 3 ● Macros, Subroutines and Bank Switching

IF_REG_LESS_THAN_W MACRO ARG1 SUBWF ARG1, W BTFSS STATUS, C

;See SUBWF on page 320,

if W > ARG1, C = 0

ENDM ; The ‘if’ code here

- this will be a GOTO to jump over the else code.

; The ‘else’ code here.

The argument names used in the definition should be different from any register names used in your program. Here I used ARG1 in the definition. When the macro name is inserted into the program, the argument name used is TICK_COUNTER. The thing to grasp here is that this is not a new opcode or instruction created for the processor. What happens is the macro definition gets placed in our code by the assembler wherever we type its name. Argument ARG1 is substituted by the argument used in the program. 3.2.1 • Use an Include File

Macros don’t have to be in the source code file with the rest of your program. You can put them in a separate file and use the include directive at the top of the source code. This will give you access to any macros in that file. Program 3-1 shows the macro inserted in two places and the definition placed in a file called fsm_macros.inc. At the top of the source code is the line #INCLUDE . The file can be in the same directory as your source file. You will have to copy it from the chap03_source_xxx directory to the working directory. ; prog_03_01.asm ; This is Prog_02_01.asm using macros. LIST P=12f1822 #INCLUDE #INCLUDE RADIX DEC

; Default numbers to base 10.

__CONFIG MEN_OFF ) __CONFIG

0X8007, (_FOSC_INTOSC & _WDTE_OFF & _PWRTE_OFF & _CP_OFF & _BOREN_OFF & _IESO_OFF & _FC0X8008, (_LVP_ON)

CBLOCK 0x70 TICK_COUNTER ENDC ORG 0X00 GOTO START ORG 0X04 BCF INTCON, TMR0IF INCF TICK_COUNTER, F RETFIE START: MOVLB 1 BCF TRISA, 2 MOVLW 0x70 MOVWF OSCCON MOVLW 0xD7 MOVWF OPTION_REG

; ; ; ;

Interrupt vector. Clear the tmr0 overflow interrupt flag. Increment by one TICK_COUNTER. Return from interrupt.

; TRISA, OSCCON, OPTION_REG. ; Make RA2 an output. ; Page 65 32MHz. ; Page 164.

● 55

PProgramming the Finite State Machine with 8-Bit PICs in Assembly and C MOVLW 0xE0 MOVWF INTCON MOVLB 0

; Page 86. ; Back to bank 0 for the rest of the program.

;------Machine states. S0 MOVLW 31 IF_REG_LESS_THAN_W TICK_COUNTER GOTO S0 CLRF TICK_COUNTER BCF PORTA, 2 S1

; LED on. ; See macro in fsm_macros.inc. ; Reset the counter. ; Turn the LED off. ; LED off.

MOVLW 91 IF_REG_LESS_THAN_W TICK_COUNTER GOTO S1 CLRF TICK_COUNTER BSF PORTA, 2 GOTO S0

; See macro in fsm_macros.inc. ; Reset the counter. ; Turn the LED on.

END

Program 3-1 Macro Demonstration

Here is an excerpt from the .lst file that you can view after running gpasm to assemble the code. It shows how the macro has been inserted with ARG1 changed to TICK_COUNTER. The .lst file is a file produced by gpasm that can be useful for debugging. This excerpt can be found by line 379 of the .lst file. 0010

301F

00037

MOVLW 31

00038

IF_REG_LESS_THAN_W TICK_COUNTER

0011

0270

M

SUBWF TICK_COUNTER, W

0012

1C03

M

BTFSS STATUS, C

0013

2810

00039

GOTO S0

One thing to watch out for is: if you are doing a jump such as GOTO $-n where n is so many lines and you are jumping over a macro, count the lines of actual code in the macro. The macro name does not count, nor does the ENDM. The snippet above from the .lst files shows the program count in the leftmost column. 3.2.2 • More Macros

We can now shorten our source files and make them more readable by having easier understood names for blocks of code that can be put out of sight. This is a great saving if these blocks are used several times. I will present some more macros that we will use. To set oscillator frequency to 32MHz: SET_FREQ_32MHZ MACRO MOVLB 1 MOVLW 0x70 MOVWF OSCCON

; See page 65.

ENDM

To set up timer0 as we have used it to generate an overflow interrupt at a rate of 122.07 interrupts per second (every 8.2 ms):

● 56

Chapter 3 ● Macros, Subroutines and Bank Switching

SET_TMR0_CASE1 MACRO

; Bank 1 required. Gives 8.2 ms ticks.

MOVLW 0xD7

; See Table 2-2 in the book.

MOVWF OPTION_REG

; Page 164.

MOVLW 0xE0 MOVWF INTCON

; Page 86 in the datasheet and Table 2-3 in the book.

ENDM

; Leaves in bank 1

Another one to set up timer0 to generate an overflow interrupt at a rate of 976.56 interrupts per second (every 1.024 ms). The difference is the Prescaler ratio is 1/32 instead of 1/256. SET_TMR0_CASE2 MACRO

; Bank 1 required. Gives 1.024 ms ticks.

MOVLW 0xD4

; See Table 2-2 in the book.

MOVWF OPTION_REG

; Page 164.

MOVLW 0xE0 MOVWF INTCON

; Page 86 in the datasheet and Table 2-3 in the book.

ENDM

; Leaves in bank 1

To save space in the book, a macro to set the bits in the configuration words: BOOK_CONFIGURATION MACRO __CONFIG 0X8007, (_FOSC_INTOSC & _WDTE_OFF & _PWRTE_OFF & _CP_OFF & _BOREN_OFF & _IESO_OFF & _FCMEN_OFF ) __CONFIG

0X8008, (_LVP_ON)

ENDM

Here is another if ...else macro to compare a register value to see if it is greater than or equal to the value in the working register. There is only one difference between this and the other if else macro. This is the skip is on carry clear instead of carry set. IF_REG_GREATER_THAN_OR_EQUAL_W MACRO ARG1 SUBWF ARG1, W BTFSC STATUS, C

; See SUBWF on page 320, if ARG1 >= W,

C = 1

ENDM ; The ‘if’ code here

- this will be a GOTO to jump over the else code.

; The ‘else’ code here.

Program 3-2 is program 2-2 but using macros. The macros are shown in bold. ; ; ; ;

prog_03_02.asm This is prog_02_02.asm using macros. Page numbers refer to the data sheet DS40001413E. Tables refer to the book.

LIST P=12f1822 #INCLUDE #INCLUDE RADIX DEC

; Default numbers are to base 10.

BOOK_CONFIGURATION

; See macro in fsm_macros.inc.

● 57

PProgramming the Finite State Machine with 8-Bit PICs in Assembly and C CBLOCK 0x70 TICK_COUNTER PULSE_COUNTER ENDC

; In commom RAM, accessible in all banks.

ORG 0X00 GOTO START ORG 0X04 ; Interrupt vector. BCF INTCON, TMR0IF ; Clear the tmr0 overflow interrupt flag. ITCON is in all banks. INCF TICK_COUNTER, F ; Increment by one counter_off. RETFIE ; Return from interrupt START MOVLB 1 ; To access TRISA, OSCCON, OPTION_REG. BCF TRISA, 2 ; Configure PORTA bit 2 (chip pin 5) as an output SET_FREQ_32MHZ ; See macro in fsm_macros.inc. SET_TMR0_CASE1 ; See macro in fsm_macros.inc. MOVLB 0 ; To access PORTA for the rest of the program. BSF PORTA, 2 ; Inialise LED on; ;-----------Machine States. S0 ; LED on. Fast cycle. MOVLW 4 IF_REG_LESS_THAN_W TICK_COUNTER GOTO S0 ; C in STATUS found not set. Stay in this state. BCF PORTA, 2 ; Turn LED off CLRF TICK_COUNTER ; Reset the tick counter. INCF PULSE_COUNTER, F ; Add 1 to PULSE_COUNTER and put the result in PULSE_COUNTER. ; Continue to S1 S1

; LED off. Fast cycle. MOVLW 10 IF_REG_LESS_THAN_W PULSE_COUNTER GOTO $+3 ; Have not reached 10 pulses jump to ticks check. CLRF PULSE_COUNTER ; 10 Pulses have been counted, reset PULSE_COUNTER. GOTO S3 ; Transition to S3. MOVLW 9 ; Load W with 9 for ticks check. IF_REG_LESS_THAN_W TICK_COUNTER ; See macro in fsm_macros.inc. GOTO S1 ; C in STATUS found set, 9 ticks counted. CLRF TICK_COUNTER BSF PORTA, 2 ; Turn LED on. GOTO S0

S2 MOVLW 32 IF_REG_LESS_THAN_W TICK_COUNTER GOTO S2 BCF PORTA, 2 CLRF TICK_COUNTER INCF PULSE_COUNTER, F

; ; ; ; ; ;

S3

C in STATUS found not set. Turn LED off Reset the tick counter. Add 1 to PULSE_COUNTER and put the result in PULSE_COUNTER. Continue to S3

; LED on. Slow cycle. MOVLW 3 IF_REG_LESS_THAN_W PULSE_COUNTER GOTO $+3 CLRF PULSE_COUNTER GOTO S1 MOVLW 91 IF_REG_LESS_THAN_W TICK_COUNTER GOTO S3 CLRF TICK_COUNTER BSF PORTA, 2 GOTO S2

; ave not reached 3 pulses jump to ticks check. ; Reset PULSE_COUNTER. ; Transition to S1.

; C in STATUS found set. ; Turn LED on.

END

Program 3-2 Using the New Macros

We can take a complete finite state machine such as M1 from Program 2-3 in chapter 2 and create a macro. Then any time we want to drive a seven-segment display, we only have to add one line of code. Program 3-3 shows this. The macro insertions are shown in

● 58

Chapter 3 ● Macros, Subroutines and Bank Switching

bold. Again if you assemble this code and look in the .lst file you will see how the macro arguments FLGS, VLUE, S0 and S1 have been substituted with FLAGS, VALUE M1_S0 and M1_S1 in the program. ; prog_03_03.asm ; This is prog_02_03.asm using macros. LIST P=16f1823 #INCLUDE #INCLUDE RADIX DEC

; Default numbers to base 10.

BOOK_CONFIGURATION

; See macro in fsm_macros.inc.

CBLOCK 0x70 TICK_COUNTER STATE_M0 STATE_M1 PULSE_COUNTER FLAGS

; Bit 0 will be the display update signal.

ENDC ORG 0X00 GOTO START ORG 0X04 BCF INTCON, TMR0IF INCF TICK_COUNTER, F RETFIE START: MOVLB 1 BCF TRISA, 2 BCF TRISA, 4 CLRF TRISC SET_FREQ_32MHZ SET_TMR0_CASE1 CLRF STATE_M0 CLRF STATE_M1 BCF FLAGS, 0 CLRF PULSE_COUNTER CLRF TICK_COUNTER MOVLB 2 BSF LATA, 2 MOVLW 0xFF MOVWF PORTC BSF LATA, 4

; ; ; ;

Interrupt vector. Clear the tmr0 overflow interrupt flag. Increment by one counter_on. Return from interrupt.

; ; ; ; ; ; ; ; ; ; ; ; ; ;

OSCCON, OPTION_REG, TRISA, TRISC Configure PORTA bit 2 as an output. Configure PORTA bit 4 as an output. Configure all of PORTC as outputss. See macro in fsm_macros.inc. See macro in fsm_macros.inc. Initialise STATE_M0 to 0. Initialise STATE_M1 to 0. Initialise update signal. Initialise PULSE_COUNTER to 0. Initialise TICK_COUNTER to 0. To access LATA and LATC for the rest of the program. Initialise LED on For common cathode 0x00. Initialise blank display.

; For common cathode use BCF. Initialise blank display.

SWITCH_M0 MOVF STATE_M0, W BRW GOTO M0_S0 GOTO M0_S1

; ; ; ;

Move STATE_0 to the working register. Program jumps by the value moved into the working register. Here if W is 0. Here if W is 1.

SWITCH_M1 MOVF STATE_M1, W BRW GOTO M1_S0 GOTO M1_S1

; ; ; ;

Move STATE_0 to the working register. Program jumps by the value moved into the working register. Here if W is 0. Here if W is 1.

M0_S0 ; LED on. MOVLW 31 IF_REG_LESS_THAN_W TICK_COUNTER GOTO SWITCH_M1 ; C in STATUS found not set. INCF STATE_M0, F ; Tansition to state 1. BCF LATA, 2 ; Turn LED off CLRF TICK_COUNTER ; Reset TICK_COUNTER INCF PULSE_COUNTER, F ; Add one to the pulse count. BSF FLAGS, 0 ; Set bit 0 of FLAGS GOTO SWITCH_M1

● 59

PProgramming the Finite State Machine with 8-Bit PICs in Assembly and C M0_S1 ; LED off MOVLW 10 IF_REG_LESS_THAN_W PULSE_COUNTER GOTO $+2 ; If pulse count < 10 skip clearing PULSE_COUNTER. CLRF PULSE_COUNTER ; On the state diagram this is shown as leaving and reentering ; the state. In practice we just stay in the state. MOVLW 91 IF_REG_LESS_THAN_W TICK_COUNTER GOTO SWITCH_M1 ; C in STATUS found not set. Staying in this state. INCF STATE_M0, F ; Skipped to here as time has expired. CLRF TICK_COUNTER ; Clear TICK_COUNTER. BSF LATA, 2 ; Turn LED on. CLRF STATE_M0 ; Change to state 0 GOTO SWITCH_M1 SEVEN_SEG_DRIVE_MACHINE FLAGS, PULSE_COUNTER, M1_S0, M1_S1 END

Program 3-3 Complete State Machine as a Macro

When using a macro, take time to read it. Remember you are substituting this code into your code, so variable names must not clash or be missing. When the code is substituted by the assembler, labels M1_S0, M1_S1, etc are used. See the program and the macro file included. 3.2.3 • Conditional Assembly

One of the macros above is SET_FREQ_32MHZ to set the oscillator frequency. We could write more macros, one for each available frequency, but there is another way allowing us to select different code within the macro, depending on the value of the argument to the macro. Conditional assembly has if statements selecting different assembly code to be placed in our source code. Here is an example for frequency selection. The macro takes an argument specifying the required frequency and the macro inserts the relevant code according to the if statements. Each if statement is terminated by an endif. SET_FREQ_HZ MACRO ARG0 if (ARG0 == 32000000); MOVLW 0x70; endif; if (ARG0 == 16000000) MOVLW 0x78; endif; if (ARG0 == 4000000) MOVLW 0x68; endif; if (ARG0 == 2000000) MOVLW 0x60;

● 60

Chapter 3 ● Macros, Subroutines and Bank Switching

endif; if (ARG0 == 1000000) MOVLW 0x58; endif; if (ARG0 == 500000) MOVLW 0x50; endif; if (ARG0 == 125000) MOVLW 0x40; endif; if (ARG0 == 31250) MOVLW 0x18; endif; MOVLB 1 MOVWF OSCCON; ENDM

The if statements are not assembly code. They belong to the assembler to select the assembly code that goes into your source code before the generation of the hex file. This example was written for gpasm version 1.4.0 to be compatible with the current version of Debian. 3.3 • Subroutines

Another way of writing reusable code is to use a subroutine. A subroutine is a chunk of code that stands apart from your main program. It gets called as required. When it gets called, the main program is temporarily halted. The subroutine code runs and then when it finishes, and the main program continues. We could achieve the same result by using macros, however every time you use a macro the code that makes up the macro gets inserted into the code and you end up with lots of duplicated code taking up program memory space. The subroutine only appears once in the program memory. The downside is that there is a time overhead in calling a subroutine. The decision to use a subroutine or a macro depends on how you want to optimize your code. If the macro is long, then a subroutine would save more space. If the macro is only a couple of lines, then the call and return overhead might make the macro a better choice. 3.3.1 • An Example of a Subroutine

To demonstrate how a subroutine call and return works, here is a short program that again flashes an LED. It will be on for 1 second and off for 2 seconds. Instead of using a timer with an interrupt, it uses a subroutine that wastes clock cycles to give delays. I have said that spinning around loops counting clock cycles is not a good thing, however, this is to merely

● 61

PProgramming the Finite State Machine with 8-Bit PICs in Assembly and C

demonstrate a subroutine call. To create a subroutine, start the routine with a label at the left end of the line and follow it with whatever code you need. End the routine with the instruction RETURN or RETLW. Refer to the datasheet on page 319 for information about RETLW. Program 3-4 shows the program. There are two states, LED on and LED off. While in each state, the subroutine called DELAY is called. This subroutine has two nested loops that count down. When finished, the program resumes on the line after the call. To set the delay period, a value is moved into BUFFER0 prior to the subroutine call. ; prog_03_04.asm LIST P=16F1823 #INCLUDE #INCLUDE RADIX DEC BOOK_CONFIGURATION CBLOCK 0x70 BUFFER0 BUFFER1 ENDC ORG 0X00 GOTO START START: CLRF BUFFER1 MOVLB 1 SET_FREQ_HZ 125000 BCF TRISA, 2 MOVLB 0 ;--------Machine states S0 MOVLW d’40’ MOVWF BUFFER0 CALL DELAY BCF PORTA, 2

S1 MOVLW d’80’ MOVWF BUFFER0 CALL DELAY BSF PORTA, 2 GOTO S0 ;----------Subroutine DELAY DECFSZ BUFFER1, F GOTO $-1 DECFSZ BUFFER0, F GOTO DELAY RETURN

; Default numbers to base 10. ; See fsm_macros.inc

; Page 65 125kH, and fsm_macros.inc. ; Make RA2 an output, in bank 1 from the macro. ; To access PORTA for the rest of the program.

; ; ; ; ; ;

LED on. 40 gives ~0.99 seconds. Load buffer prior to subroutine call. Call subroutine. Turn LED off. On to state 1 on return.

; ; ; ; ; ;

LED off. 80 gives ~1.99 seconds. Load buffer prior to subroutine call. Call subroutine. Turn LED on. Back to state 0.

; Go round 256 times, inner loop total of 256*3 + 1 = 769 ; instruction cycles. ; Go round by the number of times loaded into BUFFER0, outer loop. ; Return from subroutine.

END

Program 3-4 Example of calling a subroutine

● 62

Chapter 3 ● Macros, Subroutines and Bank Switching

3.3.2 • Return Address and the Stack

When the CALL instruction is used, the value of the program counter plus 1 (PC+1) is pushed on to the top of the stack. The stack was mentioned before when describing the return from interrupts in the previous chapter. The location of the subroutine is then moved into the program counter so that the subroutine is now executed. If you look at page 39 in the datasheet there is an explanation of the stack. As mentioned in Chapter 1, these two PICs only have 2048 program words and therefore the programs will not cross a page boundary. Refer to the datasheet page 18. 3.3.3 • Calculating the Delay

Here is an explanation of how the time delay was calculated: The inner loop goes around 256 times because it starts at 0 and decrements by one each loop back to zero. DECFSZ BUFFER1, F GOTO $-1

#

; $ is the current address in the program so $-1 is back one.

Each loop is made up of 3 instruction cycles: one for the DECFSZ and two for the GOTO. There is one extra for the final DECFSZ as it is two instruction cycles when the skip is required. Refer to page 315 in the datasheet. Consequently, each call to the inner loop from the outer loop needs 769 instruction cycles. 256x3 + 1 = 769 The outer loop runs around by the number put into BUFFER0 before the subroutine call. Every time the outer loop goes around, it uses another batch of 769 inner loop cycles, also each outer loop has its own 3 instructions cycles per lap, plus one for the final DECFSZ skip and finally the 2 instruction cycles for the subroutine call and 2 for the return instruction. If we put 40 into BUFFER0, it will give a total of 30885 instructions. 769x40 + 40x3 + 1 + 2 + 2 = 30885 instruction cycles per subroutine call. As the clock frequency is 125kHz, the time interval of one instruction is as follows: 4/125000 = 32 microseconds This gives an on flash subroutine time as 1.02 seconds. 30885 x 32x10-6 = 1.019 seconds

● 63

PProgramming the Finite State Machine with 8-Bit PICs in Assembly and C

This is not the end of the story. There are three more instruction cycles in state S0 that add a further 96 microseconds. The point to note is that it is the number of cycles inside a loop that matter. Their time period gets multiplied by the number of times around the outside loop. The same explanation can be applied to S1 state for the LED off period. Furthermore, oscillator frequency can be trimmed by setting bits in the OSCTUNE register. See page 67 of the datasheet. 3.3.4 • Calling Subroutines from Subroutines

The purpose of the stack is to store return addresses for subroutines and interrupts. It is 16 deep so it is possible to call subroutines from subroutines and to return in reverse to the main program. See the diagrams starting on page 39 of the datasheet. To demonstrate this, Program 3-5 splits the two loops into two subroutines so that the first subroutine gets called by the main program and then calls the second subroutine. The second subroutine contains one loop that goes around 256 times, each time it is called. The second subroutine gets called again and again by the first subroutine. The number of times is determined by the value placed in BUFFER0 by the main program before calling the first subroutine.

● 64

Chapter 3 ● Macros, Subroutines and Bank Switching ;prog_03_05.asm LIST P=16F1823 #INCLUDE #INCLUDE RADIX DEC

; Default numbers to base 10.

BOOK_CONFIGURATION CBLOCK 0x70 BUFFER0 BUFFER1 ENDC ORG 0X00 GOTO START START: CLRF BUFFER1 MOVLB 1 SET_FREQ_HZ 125000 BCF TRISA, 2 MOVLB 0 ;---Machine states S0 MOVLW d’40’ MOVWF BUFFER0 CALL DELAY_OUTER BCF PORTA, 2

S1 MOVLW d’80’ MOVWF BUFFER0 CALL DELAY_OUTER BSF PORTA, 2 GOTO S0

; ; ; ;

OSCCON, OPTION_REG See page 65 125kH, and fsm_macros.inc. Make RA2 an output. To access PORTA for the rest of the program.

; ; ; ; ; ;

LED on 40 gives ~0.99 seconds. Load BUFFER0 prior to the subroutine call Call the subroutine Turn LED off. Continue on to state 1 on return from the subroutine.

; ; ; ; ; ;

LED off. 80 gives ~1.99 seconds. Load buffer prior to subroutine call Call subroutine. Turn LED on. Back to state 0.

;----subroutine DELAY_OUTER DELAY_OUTER CALL DELAY_INNER DECFSZ BUFFER0, F GOTO $-2 RETURN ;return to main program ;---subroutine DELAY_INNER DELAY_INNER DECFSZ BUFFER1, F GOTO $-1 RETURN ; Return to the DELAY_OUTER subroutine. END

Program 3-5 Example of Calling a Subroutine from a Subroutine

3.4 • Bank Switching

In Chapter 1 paragraph 1.3.6, I stated that in instructions that point to a special function register, for example, bit clear file BCF, there are only seven bits allocated for the address of the register. This means that we can only point to an address range of 0 to 127 (128 locations). To get the necessary special function register, we need a way to point to the required bank. If you look at page 22 in the datasheet you can see that PORTA in bank 0 is next to TRISA in bank 1. PORTA is at address 07Ch. TRISA is at 08Ch. These addresses are expressed in hexadecimal. They are two special function registers with the same offset in their own bank. To get the correct bank, we have to put that information into the bank select register. BSR is always accessible as it is one of the 12 core registers appearing in all banks.

● 65

PProgramming the Finite State Machine with 8-Bit PICs in Assembly and C

The MOVLB instruction can be used to move the bank number into BSR. Alternatively, the gpasm BANKSEL directive can be used to generate the required code for example. BANKSEL TRISA This will save you from looking up the bank number for TRISA. Although this can be convenient, there are times when you need to know the required bank number. For example, if you need to access several special registers whilst configuring say the oscillator, the interrupts, and the inputs and outputs, you can save on bank switching if you write your code so the special registers are grouped by bank. As programs become more complicated, the problem of bank switching becomes more difficult. Because of GOTOs and returning from subroutines, you can easily be in the wrong bank for the subsequent instruction. It is one of the first places to look if the code does not run. Interrupts have automatic context saving. Therefore, the value in BSR is restored on return so you only need to change the bank within the interrupt. Refer to page 85 on the datasheet. If you are programming in C, the compiler will handle bank switching for you. My suggestion is to inspect your code by looking at small blocks such as the code for each machine state, subroutine or macro. By just looking at that block of code you can tell that it is in the correct bank for the instructions in it. You might well end up with redundant bank switching but you can always optimize it once you have it running. Be extra careful with macros where code might be out of sight in a different file. For macros, the policy I adopt is to only put bank switching inside a macro if there is a requirement to switch banks partway through the macro. This means you have to check to see if you are in the correct bank before entering the macro. It might seem inconvenient but if you have bank switching within the macro at the start, it could be redundant and wasteful of program memory.

● 66

Chapter 4 ● Inputs and Outputs

Chapter 4 • Inputs and Outputs 4.1 • Introduction

The first three chapters only used PIC outputs to drive LEDs. This was to simply get up and running with the toolchain and the concept of FSM. This chapter will elaborate on inputs and outputs and deal with digital/analog inputs, pulse width modulated outputs and serial communication with a computer. PICs can also communicate with other devices using the I2C bus and accept inputs by finger touching using capacitive sensing. The book will not go into these however from what has been covered and with the information from the datasheet, you should be able to make use of these in your projects. From a state machine point of view, the programs in this chapter are trivial This chapter is about using the I/O. 4.2 • Serial Output to a Computer

These PICs have an ‘Enhanced Universal Synchronous Asynchronous Receiver Transmitter’ (EUSART) that we can use with an FTDI serial lead to communicate directly between the PIC and computer. Contained within the archive file download for both Windows and Linux, is a program called serial_reader that prints to screen values transmitted by the PIC. To produce data to transmit, program 3-1 will flash an LED and increment a counter. The counter value will be transmitted over the serial link to the computer where it will be displayed on the screen.

Figure 4-1 State Diagram for Program 4-1

4.2.1 • TTL Level Serial Communications

An RS232 serial link in general uses a negative voltage of between -3V and -25V to represent a logical 1 and a positive voltage between +3V and +25V to represent a logical 0. The PICs and FTDI lead use voltages of 0V for a logical 0 and +5V (approx) to represent a logical 1. This is very convenient as we only need one power supply voltage and no level shifting circuitry. The serial link transfers bytes of data at a rate referred to as ‘Baud’. The Baud rate

● 67

Programming the Finite State Machine with 8-Bit PICs in Assembly and C

can be regarded in this case as bits per second. We will use a rate of 19200 Baud. The bytes that are sent along the wire are made up of a leading start bit, 7 or 8 data bits, a parity bit and then 1 or 2 stop bits. The programs presented here will not use the parity bit, have one stop bit and 8 data bits. This is configured by setting values in registers then EUSART will transmit values whenever a byte is loaded into the TXREG register. Once configured, sending data requires only a couple of lines of code each time. 4.2.2 • Configuring the EUSART to Transmit a Byte

The datasheet gives a full description of the Enhanced Universal Synchronous Asynchronous Receiver Transmitter that is in both PICs. There are many more accessible features than I will use. Page 268 shows a diagram of the transmit details. Table 4-1 shows the steps necessary to configure EUSART to transmit bytes.

Purpose

Code

Comments

Enable transmission.

BSF TXSTA, TXEN

TXEN is bit 5 in the transmit status and control register TXSTA (page 277 in the datasheet).

Enable the serial port.

BSF RCSTA, SPEN

SPEN is bit 7 in the receive status and control register (page 278).

Set up a baud rate of 19200 by setting the values in the serial port baud rate generator registers SPBRGL (low order byte) and SPBRGH (high order byte), these make up a 16-bit word.

MOVLW 0x19 MOVWF SPBRGL CLRF SPBRGH

Baudrate = clock/(64*(spbrgh:spbrgl+1)) Baudrate = 19200 (page 280 and 282).

On the PIC 16F1823 the transmit pin can be either pin 13 (RA0) or pin 6 (RC4), we want pin 13.

BSF APFCON, TXCKSEL

Set bit 2 in APFCON (alternate pin function configuration) to make the transmit pin RA0 (page 114).

Table 4-1 Configuring EUSART to Transmit

This configuration of EUSART for this purpose has been made into a macro as follows. This macro is for use at 32 Mhz:

● 68

Chapter 4 ● Inputs and Outputs

SET_SERIAL_TX_16F1823 MACRO

; Bank 3 required.

BSF TXSTA, TXEN

; Enables transmit BIT 5. See page 277.

BSF RCSTA, SPEN

; Enables the serial port BIT 7. See page 278.

MOVLW 0x19

; Sets the Baudrate=clock/(64*(spbrgh:spbrgl+1))=19200 ; for clock=32MHz. See pages 280 and 282.

MOVWF SPBRGL CLRF

SPBRGH

MOVLB 2 BSF

APFCON, TXCKSEL

ENDM

; Set bit 2 of APFCON to change the use of pin 13 to be TX. See page 114. ; Leaves in bank 2

The transmission of the byte is achieved by loading a value into the TXREG register. If the Transmit Shift Register (TSR) is empty, the byte is moved immediately to the TSR. The next line tests the TRMT bit in TXSTA. This bit is low when TSR is not empty and goes high when TSR is empty. The line after loading the byte into TXREG tests the TRMT bit and the next line loops around until the byte has been transmitted. MOVFW PULSE_COUNTER MOVWF TXREG

; Loads the value of pulse_counter into TXREG for transmission.

BTFSS TXSTA, TRMT

; When the transmission has completed TRMT gets set, until then this ; line and the next loop around.

GOTO $-1 CLRF TICK_COUNTER

You can compare the state diagram Figure 4-1 with the program below. 4.2.3 • Serial Output Example

Below is the complete program to demonstrate transmitting byte values. A single byte has a maximum value of 255. If incremented by one, it rolls over to 0.

● 69

Programming the Finite State Machine with 8-Bit PICs in Assembly and C ;prog_04_01.asm LIST P=16F1823 #INCLUDE #INCLUDE RADIX DEC

; Default numbers are to base 10.

BOOK_CONFIGURATION CBLOCK 0x70 TICK_COUNTER PULSE_COUNTER ENDC

; See macro in fsm_macros.inc.

ORG 0X00 GOTO START ORG 0X04 BCF INTCON, TMR0IF INCF TICK_COUNTER, F RETFIE START: MOVLB 1 SET_FREQ_32MHZ BCF TRISA, 2 SET_TMR0_CASE1 MOVLB 3 SET_SERIAL_TX_16F1823 BANKSEL LATA BSF LATA, 2 CLRF PULSE_COUNTER

;-------Machine states S0 MOVLW 31

; ; ; ;

Interrupt vector Clear the tmr0 overflow interrupt flag. Increment by one COUNTER_ON. Return from interrupt.

; See file fsm_macros.inc. ; Set RA4 as an output for the LED. ; See file fsm_macros.inc. ; See file fsm_macros.inc, this sets TXEN, SPEN, sets the Baud ; rate and changes the use of pin 2 to be TX. ; Initialise LED on. We are in bank 2 for LATA from the macro.

; LED on

IF_REG_LESS_THAN_W TICK_COUNTER GOTO S0 BANKSEL LATA BCF LATA, 2 CLRF TICK_COUNTER S1 MOVLW 91

; Put 31 into W to compare with ; TICK_COUNTER. ; See macro in fsm_macros.inc. ; Bank 2.

; ; ; ;

LED on Put 91 into W to compare with TICK_COUNTER. See macro in fsm_macros.inc.

IF_REG_LESS_THAN_W TICK_COUNTER GOTO S1 BANKSEL LATA ; Bank 2. BSF LATA, 2 BANKSEL TXREG ; Bank 3. INCF PULSE_COUNTER, F MOVFW PULSE_COUNTER MOVWF TXREG ; Loads the value of pulse_counter into TXREG for transmission. BTFSS TXSTA, TRMT ; When the transmission has completed TRMT gets set, until then this ; line and the next loop round. GOTO $-1 CLRF TICK_COUNTER GOTO S0

END

Program 4-1 Transmitting Bytes

The circuit diagram is shown in Figure 4-2. It is the same as used for the seven-segment display but without the display.

● 70

Chapter 4 ● Inputs and Outputs

Figure 4-2 Circuit Diagram for the Serial Read and Write Programs

Assembly of the program and writing it to the PIC is the same as described in Chapter One. Once the program is loaded, the LED will flash and the flash count will be transmitted from the serial port. To read this value on your computer, you can use the program serial_reader. exe on Windows or the Linux equivalents. The serial reader programs are in the working directory and print single-byte values to screen as they are received using the TTL-2325V-WE lead. There is no need to change the electrical connections, just run the program. Figure 4-3a shows this for the 64-bit Linux version. Other versions are the same, just remember you don’t put the ./ in front of the Windows version serial_reader.exe and you use sudo in Linux to allow you to open the USB port.

Figure 4-3a Calling Serial Read

● 71

Programming the Finite State Machine with 8-Bit PICs in Assembly and C

Figure 4-3b Serial Read Output

Note when 255 is reached, the value rolls over to 0. If you add -o some_filename after the command, the output of the reader will be written to this file. The output is appended to the file so subsequent calls to the program do not destroy data previously captured. Windows: serial_reader.exe -o data.csv Linux 64 bit: sudo ./serial_reader_64 -o data.csv Linux 32 bit: sudo ./serial_reader_32 -o data.csv The program serial_reader has other command-line arguments that can be used to change its operation. These will be explained later. The file can then be opened using a text editor or spreadsheet program like excel. This works for both Windows and Linux. The serial read program prints a column of values. Each a byte value sent from the PIC’s EUSART. One point that can confuse when two devices are communicating such as here between a PIC and a computer: the output from one device is the input to another device. Hence when the PIC is transmitting, the computer is receiving and the program running on the computer is reading, that is it is an input to the computer. 4.3 • Serial Input from a Computer

The PICs can also be written to from a computer using a serial lead. The computer will transmit to the PIC. The LED is turned on for 0.25s and off for a period determined by the

● 72

Chapter 4 ● Inputs and Outputs

values written to the PIC from the computer. If the value is 0, the LED will be always on. If it is 255, it will be off for 2.1s. You might find this program hard going so I have commented nearly every line. There are only two states, LED on and off. The extra complication is due to EUSART reading the bytes from the computer. Programming in C would still require an understanding of what is going on in the PIC. Fig 4-4 shows the state diagram.

Figure 4-4 Serial Write

4.3.1 • Configuring the EUSART to Receive Bytes

What follows is similar to the description above regarding the transmission of bytes. Table 4-2 shows the steps to configure EUSART to receive bytes. The settings are almost the same. The differences are in bold. Purpose

Code

Comments

Enable reception

BSF RCSTA, CREN

CREN is bit 4 in the receive status and control register RCSTA (page 278 in the datasheet)

Enable the serial port.

BSF RCSTA, SPEN

SPEN is bit 7 in the receive status and control register (page 278)

Set up a baud rate of 19200 by setting the values in the serial port baud rate generator registers SPBRGL (low order byte) and SPBRGH (high order byte), these make up a 16-bit word.

MOVLW 0x19 MOVWF SPBRGL CLRF SPBRGH

Baudrate=clock/(64*(spbrgh:spbrgl+1)) Baudrate=19200 (page 280 and 282)

On the PIC 16F1823 the receive pin can be either pin 12 (RA1) or pin 5 (RC5), we want pin 12.

Enable the global and peripheral interrupt bits in INTCON Set the receive interrupt enable bit in the peripheral interrupt enable register 1 (page 87)

BSF APFCON,RXDTSEL

MOVLW 0xE0 MOVWF INTCON

Set bit 7 in APFCON (alternate pin function configuration) to make the receive pin RA1 (page 114) These were enabled in the previous program but for the timer0. Bits 7 and 6 are also required for the serial receive

BSF PIE1, RCIE

Table 4-2 Configuring the EUSART to receive

● 73

Programming the Finite State Machine with 8-Bit PICs in Assembly and C

For this purpose, the receive configuration of the EUSART has been made into a macro as follows. Again this is for use at 32MHz. SET_SERIAL_RX_16F1823 MACRO BSF PIE1, RCIE

; Bank 1 required. ; Set the RCIE bit in PIE1 to enable the receive interrupt, ; see

page87.

MOVLB 2 BSF APFCON, RXDTSEL

; Set bit 7 of APFCON to change the use of pin 12 (RA1) ; to be RX, ; see page 114.

MOVLB 3 BSF RCSTA, CREN

; Enables continuous receive, see page 278.

BSF RCSTA, SPEN

; Enable the serial port.

MOVLW 0x19 MOVWF SPBRGL

; Sets the baudrate=clock/(64*(spbrgh:spbrgl+1))=19200 ; for clock=32MHz.

CLRF

SPBRGH

; See pages 280 and 282.

BCF ANSELA, 1

; RA1 (RX) set as a digital input. ; The default is set as an analog when an input see page 118.

MOVLW 0xE0 MOVWF INTCON

; Page 86 in the datasheet and Table 2-3 in the book.

ENDM

; Leaves in bank 3

4.3.2 • Interrupt Service Routine

The ISR is now more complicated as we now have two possible interrupts, timer0 overflowing and the reception of serial communication. Figure 2-3 is an illustration of how an interrupt service routine has to decide the cause of the interrupt and run the appropriate code before returning to the main program. The ISR is shown below in Table 4-3.

Label

● 74

Code

Comments From the Source Code

ORG 0X04

Interrupt vector, this is where the start of the interrupt service routine is

BTFSS INTCON, TMR0IF

This tests the timer0 overflow flag bit in INTCON and skips the next line if it set

GOTO $+2

If the overflow flag is not set the program jumps to the next but one instruction

GOTO INT_TMR0

If the overflow flag is set the program jumps to the INT_TMR0 label

MOVLB 0

Select the bank for PIR1 see page 89

BTFSS PIR1, RCIF

Test the receive interrupt flag, if set skip to receive routine

RETFIE

Escape from interrupt routine if none apply.

Chapter 4 ● Inputs and Outputs

INT_RECEIVE

MOVLB 3

Select the bank for RCREG where the received byte can be recovered, the same bank as RCSTA.

BTFSC RCSTA, FERR

Test for a framing error, indicated by the stop bit being zero, see page 278

GOTO ABORT_READ

Framing error detected, therefore, value is unreliable, jump to ABORT_READ

MOVF RCREG, W

Move the value from RCREG to the working directory

MOVWF PERIOD

Received byte value from RCREG is moved to the PERIOD file

BTFSS RCSTA, OERR

RETFIE

If no overrun error return from interrupt

BCF RCSTA, CREN

Disable continuous read (CREN), see page 278 and page 274 for overrun error

BSF RCSTA, CREN

Enable CREN again

RETFIE INT_TMR0

Check for overrun error if so then skip to disable/enable continuous read to reset the error flag

Return from interrupt

BCF INTCON, TMR0IF

Clear the tmr0 overflow interrupt flag

BCF INTCON, TMR0IF

Clear the tmr0 overflow interrupt flag

INCFTICK_COUNTER, F

Increment by one

RETFIE ABORT_READ

Return from interrupt

MOVFW RCREG RETFIE

RCREG read and value discarded Return from interrupt

Table 4-3 Interrupt Service Routine 4.3.3 • Serial Input Example ; prog_04_02.asm LIST P=16F1823 #INCLUDE #INCLUDE RADIX DEC

; Default numbers are to base 10.

BOOK_CONFIGURATION CBLOCK 0x70 TICK_COUNTER PULSE_COUNTER PERIOD ENDC

; See macro in fsm_macros.inc. ; Declares files starting from 0x70 to be the following variables.

ORG 0X00 GOTO START

; Start assembling the code at location 0x00. ; Jumps past the interrupt service routine.

; End of the declaration.

; Interrupt service routine ORG 0X04 ; Interrupt vector, this is where the start of the interrupt ; service routine is. BTFSS INTCON, TMR0IF ; This tests the timer0 overflow flag bit in INTCON and skips ; the next line if it set. GOTO $+2 ; If the overflow flag is not set the program jumps to the next ; but one instruction. GOTO INT_TMR0 ; If the overflow flag is set the program jumps to the ; INT_TMR0 label.

● 75

Programming the Finite State Machine with 8-Bit PICs in Assembly and C MOVLB 0 BTFSS PIR1, RCIF RETFIE INT_RECEIVE MOVLB 3 BTFSC RCSTA, FERR GOTO ABORT_READ MOVF RCREG, W MOVWF PERIOD BTFSS RCSTA, OERR RETFIE BCF RCSTA, CREN

; Select the bank for PIR1 see page 89. ; Test the receive interrupt flag, if set skip to receive routine. ; Escape from interrupt routine if none apply. ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;

BSF RCSTA, CREN RETFIE INT_TMR0 BCF INTCON, TMR0IF ; INCF TICK_COUNTER, F ; RETFIE ; ABORT_READ MOVF RCREG, W ; RETFIE ; ;--------------End of interrupt START: MOVLB 1 BCF TRISA, 2 SET_FREQ_HZ 32000000 SET_TMR0_CASE1 SET_SERIAL_RX_16F1823 CLRF PULSE_COUNTER MOVLW 91 MOVWF PERIOD BANKSEL LATA BSF LATA, 2

; ; ; ; ; ; ;

Select the bank for RCREG where the received byte can be recovered the same bank as RCSTA. Test for a framing error, indicated by the stop bit being zero, see page 278. Framing error detected therefore value is unreliable, jump to ABORT_READ. Move the value from RCREG to the working directory. Received byte value from RCREG is moved to the PERIOD file. Check for overrun error if so then skip to disable/enable continuous read to reset the error flag. If no overrun error return from interrupt. Disable continuous read (CREN), see page 278 and page 274 for overrun error. Enable CREN again. Return from interrupt. Clear the tmr0 overflow interrupt flag. Increment by one COUNTER_ON. Return from interrupt. RCREG read and value discarded. Return from interrupt. service routine

Select the bank for TRISA, it is also the bank for the next two macros. Set RA2 pin 11 an output. See file fsm_macros.inc, stays in bank 1 See file fsm_macros.inc, stays in bamk 1 See file fsm_macro.inc. Comes out of macro in bank 3 Clear file (initialise) PULSE_COUNTER to 0 .

; Initial off period 91/122 = 0.75 s

;--------------------------machine states S0 ; LED on MOVLW 31 IF_REG_LESS_THAN_W TICK_COUNTER GOTO S0 BANKSEL LATA BCF LATA, 2 CLRF TICK_COUNTER S1 MOVF PERIOD, W IF_REG_LESS_THAN_W TICK_COUNTER GOTO S1 BANKSEL LATA BSF LATA, 2 CLRF TICK_COUNTER GOTO S0

; LED off ; PERIOD is written to by the EUSART from the computer.

END

Program 4-2 Receiving Byte

4.4 • Analog Inputs

Some of the inputs on the PIC can be configured to convert the voltage applied to them to a number from 0 to 1023 for a range of 0 – 5V. The PIC 12F1822 has four analog inputs on its only port (PORTA). The PIC 16F1823 has eight analog inputs, four on PORTA and four on PORTC. Although there are multiple analog input channels, there is only one analog to digital converter so you have to select the channel that is to use the A/D converter.

● 76

Chapter 4 ● Inputs and Outputs

Page 130 in the datasheet shows a diagram of the A/D multiplexing. The procedure for configuring an analog input channel is as follows. For this program, only one pin will be used as an analog input. The port must be configured so that the pin is an input and set as analog. • TRISA or TRISC is used for setting the pin as an input. The default setting is input. • ANSELA and ANSELC are used to set the inputs as analog. ANSELC is only on the 16F1823. The default setting is analog. The channel used by the A/D converter must be selected. • The channel select bits CHS in ADCON0 is used to select the channel. Voltage reference must be selected. Voltage reference is what the input voltage is being measured against. This can be the +5V supply, external voltage reference or internal voltage reference. • The ADPREF bits in ADCON1 are used to select voltage reference. The conversion clock must be set. Pages 131 and 132 in the datasheet explain this fully. • The ADCS bits in ADCON1 are used to select the conversion clock. The result of the conversion is a 10-bit word presented in two registers: ADRESH and ADRESL. These are the high and low bytes of the analog to digital result. We can justify the bits to the left so that the 8 highest bits are in ADRESH and the two lowest in ADRESL, or we can justify right so that the lowest 8 bits are in ADRESL with the two highest in ADRESH. Figure 16-3 in the datasheet shows this. • The justification is set by bit 7 ADFM in ADCON1. Table 4-4 shows the mapping of analog channels to pins and I/O bits. Note how the channel numbers don’t always match the I/O numbers. Be careful when setting the CHS bits in ADCON0. Analog Inputs 12F1822

16F1823

Port

Pin

I/O

A/D

Port

Pin

I/O

A/D

PORTA

7

RA0

AN0

PORTA

13

RA0

AN0

PORTA

6

RA1

AN1

PORTA

12

RA1

AN1

PORTA

5

RA2

AN2

PORTA

11

RA2

AN2

PORTA

3

RA4

AN3

PORTA

3

RA4

AN3

-

-

-

-

PORTC

10

RC0

AN4

-

-

-

-

PORTC

9

RC1

AN5

-

-

-

-

PORTC

8

RC2

AN6

-

-

-

-

PORTC

7

RC3

AN7

Table 4-4 Analog input pins

● 77

Programming the Finite State Machine with 8-Bit PICs in Assembly and C

This program is going to read analog voltages on pin 3 RA4 (AN3). It will transmit the eight most significant bits of the 10 available read from the A/D converter to be read by the computer. By ignoring the two least significant bits, we will only resolve to 20 mV instead of 5 mV. Analog-to-digital conversion is started by setting bit 1 in ADCON0 to 1. This bit has the name GO/DONE, meaning that when it is set to high it starts the conversion. When the conversion is completed the bit gets set to low by the hardware. Inside the code is a loop that waits for completion before moving on. BTFSC ADCON0, 1

; This and the next line loop around until the conversion is finished.

GOTO $-1

4.4.1 ● Setting ADCON0 and ADCON1

ADCON0

0

D

7

0

Unimplemented

6

0

5

0

4

0

3

1

2

1

1

0

GO/DONE, this will be set later when a conversion is required and read back to check for completion

0

1

ADC enable Table 4-5 ADCON0

ADCON1

6

0

ADRESH:ADRESL left-justified. Eight most significant bits in ADRESH

7

0

6

1

5

1

4

0

3

0

Unimplemented

2

0

Unimplemented

1

0

0

0

Set the ADC clock period to 2 μS for 32 Mhz clock speed. See Table 16-1 in the datasheet.

V REF + is connected to VDD Table 4-6 ADCON1

● 78

Chapter 4 ● Inputs and Outputs

4.4.2 ● Circuit and State Diagram

A straightforward way of presenting a variable voltage to test the analog input is to use a potentiometer. The value is not critical: a linear 1KΩ will be fine. As we are using supply voltage VDD as the reference, the result should sweep from 0 - 255 (looking at the 8 most significant bits in the result) as the potentiometer sweeps from 0V output to Vdd.

Figure 4-5 Circuit for Program 4-3

The state diagram is trivial and only has two states. The machine constantly transitions between a state of waiting for the a/d conversion and waiting for serial transmission. The comments in the source code explain the code’s operation.

Figure 4-6 State Chart for Program 4-3

● 79

Programming the Finite State Machine with 8-Bit PICs in Assembly and C ; prog_04_03.asm ; Page numbers refer to the data sheet. LIST P=16F1823 #INCLUDE #INCLUDE RADIX DEC ; Default numbers are to base 10. BOOK_CONFIGURATION ORG 0x00 GOTO START ORG 0x08 START: MOVLB 1 SET_FREQ_32MHZ BSF TRISA, 4 MOVLW 0x0D MOVWF ADCON0 MOVLW 0x60

; ; ; ; ; ; ; ;

MOVWF ADCON1 MOVLB 3 SET_SERIAL_TX_16F1823

Macros require bank 1, TRISA in bank 1. See the file fsm_macros.inc. RA4 (AN3) as input. See text in the book RA4 is AN3! Select channel AN3 and enable ADC. See table 4-5 in the book. See page 137, left-justified result, conversion clock Fosc/64, Vref is Vdd the PIC supply voltage. See table 4-5 in the book.

; see file fsm_macros.inc.

S0 BANKSEL ADCON0 BSF ADCON0, 1 BTFSC ADCON0, 1 GOTO $-1

; Bank 1 ; Start a to d conversion ; This and the next line loop around until the conversion is finished.

MOVF ADRESH, W BANKSEL TXREG MOVWF TXREG BTFSS TXSTA, TRMT

; ; ; ; ;

S1 The most 8 significant bits are in ADRESH (left justification). Bank 3 Loading TXREG starts the transmission. When the transmission has completed TRMT gets set, until then this line and the next loop around.

GOTO $-1 GOTO S0 END

Program 4-3 Read and Transmit an Analog Value

The serial_reader program provided in the working directory can accept a scaling coefficient as a command-line argument. If the input voltage is 4.99 V for the full-scale reading of 255, a scaling coefficient of 0.01957 will give the output to the screen in volts: see Fig 4-7 below. This is one of the command-line options that can be used. By adding -f followed by a scaling coefficient, the output from the program is changed from raw byte values to raw byte values and a scaled floating-point value. You can also add the -o file_name option to the command line to capture the results. Be aware that this program will create a 500k byte file in 10 seconds with approximately 20000 samples.

● 80

Chapter 4 ● Inputs and Outputs

Figure 4-7 Reading the Input Voltage

This is OK if the Vdd voltage is regulated, but if you want to make measurements independent of the supply voltage, you can use the internal voltage reference of the chip as we will see later in the book. 4.5 ● Pulse with modulated outputs

To supply a load with a variable voltage, the PICs use a technique of pulse width modulation (PWM). This involves turning the output on and off at a fast rate. Our example will be at a rate of 1.95 kHz. The frequency that the output switches remains constant but the length of time in each cycle that the output is on is varied to control the power supplied to the load. Figure 4-8 shows three traces: • • •

Trace A, the time the output is on is short with a long off-time, giving a low power output. Trace B Trace C the on-time is long with a short off-time giving a high output power. The PIC allows us to vary this on to off ratio with up to a precision of one part in 1024 that is a 10 bit resolution.

The great advantage of PWM is that when a perfect switch is on, it has current flowing through it but no voltage across it. When off, it has a voltage across it but no current through it. Therefore it does not dissipate any power. If you tried to control the output power by using a series pass transistor, there would be power loss in the transistor. However, the output is not a perfect switch but the losses are very small. The voltage at the output is either 5V or 0V but can be made to appear like the average voltage by connecting a capacitor across the output. This is not required for an LED as your eye averages the brightness.

● 81

Programming the Finite State Machine with 8-Bit PICs in Assembly and C

Figure 4-8 Pulse Width Modulation 4.5.1 ● LED with Pulsing Brightness

To demonstrate the use of PWM, Program 4-4 varies the brightness of an LED in cycles through bright and dim. The LED should be connected through a resistor as previously but to RC5. Starting with the state diagram Figure 4-9, there are four states. In state 0, the brightness is constant for 41 ms after which the brightness is incremented by 1 and the program jumps to state 1. If the maximum brightness has been reached, the machine jumps to state 4 and resets TICK_COUNTER to zero. If maximum brightness has not been reached, the state changes back to 0 and this cycle repeats. When maximum brightness has been reached and the state is 4, the state holds for 41 ms and the state changes to 3 and the brightness is decremented by 1. If the brightness is above the minimum, the state returns to 4 and TICK_COUNTER is reset to zero.

Figure 4-9 State Diagram Prog 4-4

● 82

Chapter 4 ● Inputs and Outputs

When the brightness reaches the minimum, the state changes from 3 to 0, back to the start condition. The PICs have complicated features associated with PWM peripherals. Here we will use a simple example of its configuration. I will show how this example is set up using tables explaining the bit values used. The datasheet gives full explanations. Four special-purpose registers have to be configured: PR2, T2CON, CCP1CON, and CCPR1L. The period of a PWM cycle is the inverse of its frequency. As mentioned above, this frequency will be 1.95 kHz. Timer2 is used to determine the frequency. On page 176 of the datasheet, Fig 22-1 is a block diagram of the timer. Register TMR2 is incremented by pulses from a Prescaler that is fed from Fosc/4. When the value in TMR2 reaches the value in PR2, a comparator gives an output and the register TMR2 is reset to zero. Now if you look at page 195 of the datasheet, on Fig 24-4 you can see that this is used in conjunction with another comparator and an S-R flip flop. The overview explanation is that Timer2 sets the flip flop at regular intervals that are the period of the PWM cycle and partway through the cycle the flip flop is reset by the other comparator comparing the value that comes from CCPR1L. This means that we can control the output power by changing the values in CCPR1L (eight bits) and two additional bits that come from bits CCP1CON . The two bits from CCP1CON are the least significant bits so we will not use them. The power will be adjusted by varying CCPRIL over its range of 0 to 255. The T2CON special register is set up to give the PWM frequency of 1.95 kHz. Table 4-7 shows the settings. The Prescaler value of 16 comes from Table 24-5 on page 197 of the datasheet. To explain this setting, the oscillator frequency will be 32MHz. The Prescaler is fed with Fosc/4. The Prescaler is 16 and according to the table, PR2 will be OxFF (dec 255). This means that the period will be determined when TMR2 matches PR2. PWM frequency = 32000000/(16x256x4) = 1.95 kHz

T2CON

0

6

7

0

6

0

5

0

4

0

3

0

2

1

1

1

0

0

Unimplemented

Post scaler not used, left as default

Timer2 is on Timer 2 Prescaler set to 16 Table 4-7 T2CON

● 83

Programming the Finite State Machine with 8-Bit PICs in Assembly and C CCP1CON

0

C

7

0

6

0

5

0

4

0

3

1

2

1

1

0

0

0

Single output P1A (RC5 on the 16F1823, see page 5 of the datasheet). These are used as the least significant bits of the 10 bits to set the pulse width ratio. Set to PWM mode with P1A active high. If one of the active low modes is chosen the dim and bright phases will be reversed. Table 4-8 CCP1CON

The program is shown below. There are comments on nearly all lines. ; prog_04_04.asm ; Page numbers refer to the data sheet DS40001413E. ; Tables refer to the book. LIST P=16f1823 #INCLUDE #INCLUDE RADIX DEC

; Default numbers are to base 10.

BOOK_CONFIGURATION

; See macro in fsm_macros.inc.

CBLOCK 0x70 TICK_COUNTER ENDC

; In commom RAM, accessible in all banks. ; Only one tick counter is used.

ORG 0x00 GOTO START ORG 0x04 BCF INTCON, TMR0IF INCF TICK_COUNTER, F RETFIE START MOVLW 0xFF MOVWF PR2 MOVLW 0x06 MOVWF T2CON MOVLB 5 MOVLW 0x0C MOVWF CCP1CON CLRF CCPR1L MOVLB 1 SET_FREQ_32MHZ SET_TMR0_CASE1 BCF TRISC, 5 MOVLB 2 BCF LATA, 2 MOVLB 5

; See the text in the book reference PWM frequency. ; see Table 4-7 in the book.

; ; ; ;

See Table 4-8 in the book. Initialises the PWM to minimum output. For macros and TRISC. See the file fsm_macros.inc.

;

Sets RC5 as an output.

; Initialise LED to off. ; For CCPR1L

S0 ; LED brightness increasing states 0 and 1. MOVLW 0x5 IF_REG_LESS_THAN_W TICK_COUNTER GOTO S0 ; When tick counter is less than 5 stay in S0. S1 ; Waxing inc. INCF CCPR1L, F CLRF TICK_COUNTER ; MOVLW 255 IF_REG_GREATER_THAN_OR_EQUAL_TO_W CCPR1L

● 84

This is common to both outcomes. See state chart.

Chapter 4 ● Inputs and Outputs GOTO S3 GOTO S0 S2

S3

; When max brightness jump to S3.

; LED waning states 2 and 3. MOVLW 0x5 IF_REG_LESS_THAN_W TICK_COUNTER GOTO S2 CLRF TICK_COUNTER

; When tick counter is less than 5 stay in S2.

; LED brightness decreasing states 3 and 4. DECF CCPR1L, F CLRF TICK_COUNTER ; This is common to both outcomes. See state chart. MOVLW 1 IF_REG_GREATER_THAN_OR_EQUAL_TO_W CCPR1L GOTO S2 ; Else jump to S2. GOTO S0 ; When min brightness jump to S0. END

Program 4-4 Pulse Width Modulated Output 4.6 ● Digital Inputs

We can use I/Os RA0, RA1, RA2, RA4, RA5, RC0, RC1, RC2, RC3, RC4, and RC5 as digital inputs. We can not use RA3 as an input as we are using low voltage in-circuit programming and this pin is automatically enabled as MCLR. When pin MCLR is held low, the device is reset. The pin is held high by the internal weak pull-up resistor. To configure a pin as an input, the relevant bit in TRISA or TRISC should be set to 1. This is the default anyway. Some of the pins can be analog inputs, so their relevant bit in ANSELA or ANSELC must be cleared to 0. The default is 1. See pages 3 and 5 of the datasheet for tables giving an overview of these points. Also, be aware of the APFCON register that changes the use of some pins. We already used this to reassign the Tx pin in Program 4-1. 4.6.1 ● Counting Input Pulses From a Switch

This program will try to count pulses from a manually operated switch. The count will be read by transmitting to the computer over a serial link. The circuit diagram is shown in Figure 4-10. A switch is connected to pin 11 (RA2). By using the internal weak pull-up resistor, there is no need for an external resistor. The switch pulls the pin voltage down to 0 V when it is closed.

● 85

Programming the Finite State Machine with 8-Bit PICs in Assembly and C

Figure 4-10 Circuit for Program 4-5

Figure 4-11 shows the state diagram for the program. The program remains in state 0 while the switch is open and moves to state 1 if it closes. On transition, the pulse counter is incremented by 1 and the value transmitted. The machine remains in state 2 until the switch opens again when it then transitions to state 0.

Figure 4-11 State Diagram for Program 4-5

The program below has comments on almost every line. By running the serial read program, you can see the counts incrementing every time you close the switch. Depending on the switch you are using, there might be a problem though. If you are just touching a wire end to use as a switch, the problem will be very pronounced. This is the problem of switch bounce. Switch bounce is when the switch does not close or open cleanly and bounces several times. The PIC is fast enough to count the bounces, so

● 86

Chapter 4 ● Inputs and Outputs

when the switch closes, several counts will be recorded, not just one. This problem can be overcome by connecting the switch to the input using a resistor-capacitor network. These, however, are more components that take up space. Switch bounce can be eliminated using software. Figure 4-12 shows an oscilloscope trace of voltage across a push switch as it closes. The time interval between the two vertical dotted ruler lines is 1ms. This is just one example. Different switches will have different characteristics and a given switch will not always respond in the same way every time.

Figure 4-12 Example of a Switch Contact Bounce

● 87

Programming the Finite State Machine with 8-Bit PICs in Assembly and C ;prog_04_05.asm LIST P=16F1823 #INCLUDE #INCLUDE RADIX DEC

; Default numbers are to base 10.

BOOK_CONFIGURATION CBLOCK 0x70 PULSE_COUNTER ENDC

; See macro in fsm_macros.inc.

ORG 0X00 MOVLB 1 BCF OPTION_REG, 7 SET_FREQ_32MHZ

MOVLB 3 BCF ANSELA, 2 SET_SERIAL_TX_16F1823

CLRF PULSE_COUNTER BANKSEL PORTA

; Weak pull ups enable ; See file fsm_macros.inc.

; Set RA2 as a digital input. ; See file fsm_macros.inc, this sets TXEN, SPEN, sets the Baud ; rate and changes the use of pin 2 to be TX. ; Initialize count to zero.

;-------Machine states S0 BTFSC

PORTA, 2

GOTO S0 INCF PULSE_COUNTER, F MOVF PULSE_COUNTER, W BANKSEL TXREG MOVWF TXREG BTFSS TXSTA, TRMT

; ; ; ;

If switch is open input will be high weak pull-up resistor will make input high. Weak pull-ups are enabled by default. See page 119 in the datasheet. If high no skip so program stays in state 0

; Move value in PULSE_COUNTER to W ; Loading TXREG starts the transmission. ; When the transmission has completed TRMT gets set, ; until then this line and the next loop around.

GOTO $-1 S2 BANKSEL PORTA BTFSS PORTA, 2

; If switch is closed input will be low. No skip the program ; stays in state 2.

GOTO S2 GOTO S0 END

Program 4-5 Counting Input Pulses Without Switch Bounce Elimination

4.6.2 ● First method eliminating the effect of switch bounce

One software method of eliminating the effect of switch bounce is to introduce a time delay after the first detection of a switch opening or closing. This is the method used in Program 4-6. It is not the best way of dealing with the problem as it causes the program to stall for a short time: in this case about 33 ms. Although this is not noticeable to a human, in a busy program it could be undesirable. At a clock frequency of 32MHz, this is 264000 instructions. Period of one instruction = 4/(32x106) = 1.25x10-7 s Number of instructions in 33 ms = 33x10-3/1.25x10-7 = 264000 instructions

● 88

Chapter 4 ● Inputs and Outputs ;prog_04_06.asm LIST P=16F1823 #INCLUDE #INCLUDE ; This is not the best way to deal with the problem of switch bounce. RADIX DEC ; Default numbers are to base 10. BOOK_CONFIGURATION CBLOCK 0x70 PULSE_COUNTER TICKS ENDC

; See macro in fsm_macros.inc.

ORG 0X00 GOTO START ORG 0X04 BCF INTCON, TMR0IF INCF TICKS, F RETFIE START MOVLB 1 SET_TMR0_CASE1 BCF OPTION_REG, 7 SET_FREQ_32MHZ MOVLB 3 BCF ANSELA, 2 SET_SERIAL_TX_16F1823

; ; ; ;

Interrupt vector. Clear the tmr0 overflow interrupt flag. Increment TICKS by 1. Return from interrupt.

; ; ; ;

See file fsm_macros.inc. Weak pull ups enable, this has to come after SET_TMR0_CASE1 because that macro changes the OPTION_REG. See file fsm_macros.inc.

; Set RA2 as a digital input. ; See file fsm_macros.inc, this sets TXEN, SPEN, sets the Baud ; rate and changes the use of pin 2 to be TX.

CLRF PULSE_COUNTER

; Initialize count to zero.

;-------Machine states S0 BANKSEL PORTA BTFSC PORTA, 2

GOTO S0 CALL DELAY_33_ms

; ; ; ;

If switch is open input will be high weak pull up resistor will make input high. Individual weak pull ups are enabled by default. See page 119 in the data sheet. If high no skip so program stays in state 0

; Call the 33 ms subroutine.

S1 INCF PULSE_COUNTER, F MOVF PULSE_COUNTER, W BANKSEL TXREG MOVWF TXREG BTFSS TXSTA, TRMT

; Move value in PULSE_COUNTER to W ; Loading TXREG starts the transmission. ; When the transmission has completed TRMT gets set, until then this ; line and the next loop round before moving to state 2.

GOTO $-1 S2 BANKSEL PORTA BTFSS PORTA, 2 GOTO S2 CALL DELAY_33_ms GOTO S0

; If switch is closed input will be low. No skip, program ; stays in state 2. ; Call the 33 ms subroutine.

; Subroutine DELAY_33_ms BANKSEL TMR0 CLRF TMR0 ; Start the subroutine with TMR0 at 0. CLRF TICKS ; Start the subroutine with TICKS at 0. LOOP MOVLW 4 IF_REG_LESS_THAN_W TICKS GOTO LOOP ; Stay in the subroutine until 4 ticks of 8.2 ms. RETURN ; Return from subroutine. END

Program 4-6 Counting Input Pulses with a Time Delay to Eliminate Bounce Effect

● 89

Programming the Finite State Machine with 8-Bit PICs in Assembly and C

This method works and is fine here where the program has nothing else to do. The next program takes a different approach where there is no blocking of the program so that if required other things can be done while waiting for the switch contacts to settle down. 4.6.3 ● A Better Method of Eliminating the Effect of Switch Bounce

This program might seem complicated and difficult to follow as it uses two finite state machines running together as threads. The state machine technique keeps the different functions separate and easy to write. The first machine M0, is the same as the previous program except that it does not look at the input from the switch directly. It is informed of the switch position by a signal from the other machine, M1. Machine M1 monitors the input directly and if the switch moves to open from closed or from closed to open, it starts counting 1ms interrupt ticks. Only if the counter reaches 25 without the input bouncing, does it change the signal that is used by M0. The way the two machines run together is: once a block of state code is executed in one machine, the program executes a block of state code in the other machine. No spinning loops are used as time delays except for waiting for serial transmission of one byte to complete. This means the processor is not wasting cycles. Figure 4-13 shows the state diagram for Program 4-7.

Figure 4-13 State Diagram for Program 4-7

● 90

Chapter 4 ● Inputs and Outputs ;PROG_04_07.asm LIST P=16F1823 #INCLUDE #INCLUDE RADIX DEC BOOK_CONFIGURATION CBLOCK 0x70 PULSE_COUNTER TICKS STATE_M0 STATE_M1 CONTACT_SIGNAL ENDC

; Default numbers are to base 10. ; See macro in fsm_macros.inc.

; Debounced signal in bit 0.

ORG 0X00 GOTO START ORG 0X04 BCF INTCON, TMR0IF INCF TICKS, F RETFIE START: MOVLB 1 SET_TMR0_CASE2 BCF OPTION_REG, 7 SET_FREQ_32MHZ MOVLB 3 BCF ANSELA, 2 SET_SERIAL_TX_16F1823 CLRF TICKS CLRF STATE_M0 CLRF STATE_M1 CLRF PULSE_COUNTER BANKSEL PORTA SWITCH_M0 MOVF STATE_M0, W BRW GOTO M0_S0 GOTO M0_S1 SWITCH_M1 MOVF STATE_M1, W BRW GOTO M1_S0 GOTO M1_S1 GOTO M1_S2 GOTO M1_S3

; Interrupt vector. ; Clear the tmr0 overflow interrupt flag. ; Return from interrupt.

; Gives 1 ms ticks. See fsm_macros.inc ; Weak pull ups enable. ; See file fsm_macros.inc. ; ; ; ;

Set RA2 as a digital input. See file fsm_macros.inc, this sets TXEN, SPEN, sets the Baud rate and changes the use of pin 2 to be TX. Initialize count to zero.

; Move STATE_0 to the working register. ; Program jumps by the value moved into the working register.

; Move STATE_1 to the working register. ; Program jumps by the value moved into the working register.

;-------Machine states M0_S0 BTFSC CONTACT_SIGNAL, 0 ; Using bit 0 as contacts’ variable. Skip if close that is low. GOTO SWITCH_M1 ; Remain in state 0 awaiting contact closure. INCF PULSE_COUNTER, F MOVF PULSE_COUNTER, W ; Move value in PULSE_COUNTER to W. BANKSEL TXREG MOVWF TXREG ; Loading TXREG starts the transmission. BTFSS TXSTA, TRMT ; When the transmission has completed TRMT gets set, until ; then this line and the next loop around. GOTO $-1 INCF STATE_M0, F ; Make STATE_M0 1. GOTO SWITCH_M1 M0_S1 BTFSS CONTACT_SIGNAL, 0 ; Skip if opened. GOTO SWITCH_M1 ; Remain in state 1 awaiting contact opening. CLRF STATE_M0 GOTO SWITCH_M1 ;================================================================================= ; PORTA bit 0 is 0 for the contacts being CLOSED and 1 for the contacts being OPEN ;================================================================================= M1_S0 ; Awaiting closure BANKSEL PORTA BTFSS PORTA, 2 ; If electrical contacts are open, bit will be high. GOTO $+2

● 91

Programming the Finite State Machine with 8-Bit PICs in Assembly and C GOTO SWITCH_M0 CLRF TICKS INCF STATE_M1, F GOTO SWITCH_M0

; Electrical contacts open, stay in state 0. ; Electrical contacts closed, change to state 1 reset TICKS.

M1_S1 BTFSS PORTA, 2 GOTO $+3 CLRF STATE_M1 GOTO SWITCH_M0 MOVLW 4 IF_REG_LESS_THAN_W TICKS GOTO SWITCH_M0 INCF STATE_M1, F BCF CONTACT_SIGNAL, 0 GOTO SWITCH_M0 M1_S2 BANKSEL PORTA BTFSC PORTA, 2 GOTO $+2 GOTO SWITCH_M0 CLRF TICKS MOVLW 3 MOVWF STATE_M1 GOTO SWITCH_M0 M1_S3 BTFSC PORTA, 2 GOTO $+3 GOTO SWITCH_M0 MOVLW 4 IF_REG_LESS_THAN_W TICKS GOTO SWITCH_M0 CLRF STATE_M1 BSF CONTACT_SIGNAL, 0 GOTO SWITCH_M0

; If electrical contacts are open, bit will be high. ; Skipped to her because the switch bounced open.

; Time has not elapsed. ; Time has elapsed change to state 2. ; Indicating the contacts are closed.

; Awaiting opening. ; If electrical contacts are closed, bit will be low. ; Electrical contacts closed, stay in state 2 ; Electrical contacts open, change to state 3 reset TICKS.

; If electrical contacts are closed, bit will be low. ; Skipped to her because the switch bounced closed.

END

Program 4-7 Counting Input Pulses with a Separate Thread to Eliminate Switch Bounce Effect

Figure 4-14 shows how after the execution of each block of state code, the code jumps to the switch for the other machine to execute its current state block of code. This way, both machines share processor time. As the machines have access to all memory, M1 can signal to M0 when it is satisfied the switch bouncing has stopped. Don’t confuse the electrical switch with the switch code that is used to select the state block of code.

Figure 4-14 Machines Alternate Execution of Blocks

● 92

Chapter 5 ● Project Hardware Construction

Chapter 5 • Project Hardware Construction 5.1 • Introduction

Using a plug-in breadboard is a convenient way of developing software or wanting to try different circuits. Sooner or later, you will want to make a project to keep and this will require some sort of circuit board. There are many available options and if you are experienced at building you will have your favoured methods. This chapter is for those who are new to circuit construction or want to try something different. The method to be described here is my preferred method. They are, however, more practical than professional looking. They require the use of basic hand tools but a workshop is not necessary. A piece of plywood or chipboard to protect your work surface is all that is required. You will need to solder so health and safety must be considered. Soldering irons will burn you if you touch the wrong end. You could also accidentally burn through a mains supply. Also, breathing in the fumes from hot solder is not recommended. When using drills and other tools with points or sharp edges you can cut yourself. Remember to protect your eyes from solder and swarf. Be careful! The above list of mishaps is not exhaustive. I will begin by giving an overview of the suggested method and make comparison to comparable methods. This method is meant for building single projects and not for commercial production. It gives a reliable board that is easy to modify if you change your design. It is also very easy to plan the layout and the wiring. How tidy you make your boards is up to you. Ugly boards work just as well. This chapter will describe the construction of a complete project, this being a digital DC voltmeter with a range of 0V to 20V, powered by four 1.5 V cells (AA or AAA size). The display is a single-digit 7-segment LED that flashes four significant figures of the reading. It turns off when no voltage is detected for three readings. This chapter will deal only with the construction and testing of the board. The voltmeter program will be covered in Chapter 7 after binary arithmetic has been described in Chapter 6. At the end of this chapter is a test program to demonstrate the board. 5.2 • Overview of the Suggested Method

The circuit boards are built on plain, single-sided copper clad board. They are widely available, usually in Epoxy Glass Fabric Laminate or Synthetic Resin Bonded Paper. The former is more expensive. Components are mounted on the copper side by first drilling 0.5 mm diameter holes for component legs to pass through and then connecting the circuit on the non-copper side with thin single-core wires using soldered joints. The holes need to be countersunk to prevent the copper from touching the legs. The copper side of the board is used as a ground plane at 0 V. Any connections to 0 V can be soldered directly to the copper without a hole. There are a couple of other methods that also use single-sided boards, one where instead of drilling the board, small single-sided copper pads are glued to the copper side as insulated islands that the components can be soldered to. This is sometimes called Manhattan Construction. Integrated circuits e.g. PICs are glued upside down with their legs upwards. This is referred to as dead bug. I don’t like this method even though it has two advantages: there is no need to drill lots of holes and secondly, everything is accessible from one side. I find cutting and gluing small pads just as time-consuming as drilling holes

● 93

Programming the Finite State Machine with 8-Bit PICs in Assembly and C

and the fact that all the soldered joints and wiring are on the same side as the components makes the board more crowded. Another similar technique to the Manhattan method uses high-value resistors instead of the copper islands: one end of the resistor is soldered to the copper, and the other end uses as a terminal to connect components. The idea is that the high value of the resistors makes them effective insulators. Figure 5-1 gives an example of a board built using my suggested method.

Figure 5-1 Example of a Board

5.3 • Cutting and Drilling the board

Planning the circuit layout is time well spent. If you have already tested your circuit on a breadboard, you will know the amount and the physical sizes of components. When finished, you will probably want to put the board in a box of some sort. For one-off experimental projects, I find it best to wait and acquire a box big enough at the end. Lay the components out on a piece of paper starting with the larger items, usually integrated circuits, large capacitors, coils, and transformers if you need them. Fit the smaller components around them, having a thought for the distances between components that are to be connected. If carried out with insulated wires, the connections on the opposite side can be allowed to cross one another. This is one of the advantages of this method. Try to plan your layout to keep the interconnecting wires as short as possible. The circuit diagram for the voltmeter is shown in Figure 5-2. It is similar to the circuit in Figure 2-9 from Chapter 2. The 7-segment display has been omitted to save space in the diagram. This circuit is powered by four 1.5V cells. Once the PIC has had the program loaded into it, the FTDI lead can be disconnected. The 5V supply from the lead is not used, and therefore, the end of the red wire should be insulated. Once the program has been loaded into the PIC, the FTDI lead is no longer required for the project. By having the terminals for the lead, future modifications can be made to the program including using the serial interface.

● 94

Chapter 5 ● Project Hardware Construction

Figure 5-2 Voltmeter Project Schematic

Figure 5-3a Voltmeter Project Circuit Board

Figure 5-3a shows a scale drawing of the layout. Component leads pass though 0.5 mm diameter holes that are gently countersunk to clear the copper plane away from component leads. I use a 4 mm twist drill glued into a file handle as a countersinking tool. It only takes

● 95

Programming the Finite State Machine with 8-Bit PICs in Assembly and C

one or two turns to clear the copper around the hole. Terminals T1 to T8 are copper pins that pass through 1.0 mm holes. Terminals T1, and T4 to T7 pass through to the wiring side. The holes are countersunk and the pins secured with a drop of epoxy glue such as Araldite. Terminal pins T2, T3 and T8 are also inserted into 1.0 mm diameter holes, but the holes are not countersunk as the pins are soldered to the ground plane. Figure 5-3b shows details of the holes. Drill four 4 mm diameter holes at the corners of the board: these are for the fixing screws if you want to mount the board in a box. When you come to solder the wiring on the non-copper side, you can temporarily attach the screws with nuts through the holes as legs to stand the board upside down holding the components clear of the table while you solder. To cut the board to size, the easiest method is to use a metal straight edge and score a line on both sides with a modelling knife. Then clamp the board between two bits of wood and snap it along the lines.

Figure 5-3b Details of the Holes

5.4 • Populating and Wiring the Board

Once the holes have been drilled, place the terminals in the holes and solder the grounded ones and glue the insulated ones in place. A glue such as rapid Araldite can be used. For the PIC itself, I recommend using an IC holder that you place in the holes and solder the wiring to. Plug the PIC in after all the soldering is finished. This makes it less likely to damage the PIC. Should it become damaged electrically in the future you can easily change it. For the other components, bend the legs at a right angle to line up with their holes. Feed them through until the component is almost against the copper surface. Snip off all but 5mm of the legs and bend at right angles flat on the non-copper side, see Figure 5-3b. Ensure you have the diodes the correct way round. The purpose of these diodes is to protect the PIC’s input from reverse voltage, if the probe leads are accidentally used the wrong way round and an overvoltage if the input rises above the supply voltage. By using screws and nuts to make legs to stand the board upside down, it will be easy to solder the wiring while keeping the components clear of the work surface. If the IC holder drops out, it can be temporarily held in place with a bit of tape. For the connection between component legs and the IC holder pins, I find wire wrapping wire ideal as it’s one solid strand and is very thin.

● 96

Chapter 5 ● Project Hardware Construction

Figure 5-4 shows the board turned over to the wiring side with wiring shown as straight lines. If you are using insulated wire, you can connect it taking the most direct route. There is no electrical advantage and sometimes it can be a disadvantage to loom cables together just to look good. The battery and its switch are not shown on the wiring diagram. They are connected by wires to T1 and T2. As previously mentioned, this project uses a battery instead of the 5 V USB. The four 1.5 V cells can be used, as the maximum voltage that the PIC can be supplied with is 6.5 V. Refer to datasheet page 322. You must get the polarity correct. The PIC will not survive if you don’t. The wiring shown is for a common anode display. A common cathode can be used but the common terminal on the display must be taken to 0V instead of the supply and the modifications applied to the display macro.

Figure 5-4 The Wiring Side of the Board

5.5 • The Circuit Board Test Program

This project is the most complicated so far, but it draws from previous programs. With any project you are developing, it pays to build in bits and test as you go. This applies to software and hardware. Break problems down into parts and test each part. This way you can depend on what you have done before moving on. Having built the circuit board, a program will be used to test that it functions before moving on to experimenting with the

● 97

Programming the Finite State Machine with 8-Bit PICs in Assembly and C

rest of the software. Looking at the circuit diagram, the probes used to measure voltage are connected to the RA2 input configured as an AN2 analog channel through the R11, R12 potential divider. We will be configuring the PIC to compare input voltage against an internal reference of 4.096 V. To achieve 4.096 V at the input pin, the voltage across the probes will be 22.7 V: this being the full-scale reading. The maximum voltage that can be applied without damaging the PIC is 29.4 V if the supply voltage is 5 V. See page 322 of the datasheet. You must not apply a voltage greater than 0.3 V above the supply voltage to any input pin, Schottky diode D1 will be forward biased if this happens and will clamp the voltage on the input to around 0.3 V above Vss. Calculation of maximum allowable voltage across the probes for a PIC supply voltage of 5 V: Maximum voltage = (Vss + 0.3) x 122/22 V. e.g. 5.3x122/22 = 29.4 V.

Where Vss is PIC supply voltage.

This program does not display the value of the input voltage across the probes. This will be in Chapter 7. It detects if the voltage is above or below 4 V. When there is 4 V across the probes, there will be 0.721 V between the analog input pin and ground. This will produce an analog-to-digital result of 180. A/D result = (4×22/122) * 1024/4.096 = 180.3 If you look at the ADC Transfer Function graph on page 141 of the datasheet, the value of 1023 (0x3FF) is one least significant bit (LSB) short of Vref. This is why I used 1024 in my calculation. Incidentally, one LSB is 4 mV. As we are feeding the analog input pin through the potential divider, 4 mV at the input pin is 22.18 mV at the probe leads. All this program is for is to test the circuit board. When powered up the display will show 0. If 4 V or more is connected across the input probe connections or the start button is pressed, the display will step through the numbers. Fig 5-5 shows the state machines for this program. There is the macro machine M1 to drive the seven-segment LED display and the main machine M0 to control the sequencing and the A/D measurement. As before, these two machines are interlaced in that they run one block of code in one machine and then run one block of code in the other.

● 98

Chapter 5 ● Project Hardware Construction

Figure 5-5 Test Program State Diagram

5.5.1 • Analog Configuration

Tables 5-1 to 5-3 show the configuration of ADCON0, ADCON1, and FVRCON. We are using RA2, which is analog channel AN2, pin 11 for the analog input. The choices for ADCON0 can be read on page 136 of the datasheet. The choices for ADCON1 can be seen on page 137. Note the 10-bit analog result is left-justified in the two files ADRESH:ADRESL so that we will be looking at ADRESH to read the 8 most significant bits. The FVRCON register is used to configure the fixed voltage reference. It is enabled and set to 4.096 V (see pages 127 and 128 of the datasheet). To keep it simple only ADRESH, containing the 8 most significant bits is used to determine input voltage. Above it was calculated that the A/D reading would be 180 when 4 V was reached. This is ‘0010110100’B as a 10-bit binary number. We are only looking at the top 8 bits that are in ADRESH ‘00101101’B as the result is left justified, giving 45 decimal in ADRESH. The loss of two bits is not important in this program as it is a functional test of the circuit board.

● 99

Programming the Finite State Machine with 8-Bit PICs in Assembly and C ADCON0

0

9

7

0

6

0

5

0

4

0

3

1

2

0

1

0

0

1

Unimplemented

Channel Select (CHS) AN2

GO/DONE, this will be set later when a conversion is required and read back to check for completion ADC enable Table 5-1 ADCON0

ADCON1

6

3

ADRESH:ADRESL Left justified. Eight most significant bits in ADRESH

7

0

6

1

5

1

4

0

3

0

Unimplemented

2

0

Unimplemented

1

1

0

1

V REF + is connected to internal Fixed Voltage Reference (FVR) module

Set the ADC clock period to 2 μS for 32 Mhz clock speed. See Table 16-1 in the datasheet.

Table 5-2 ADCON1

FVRCON

8

3

7

1

Enable fixed voltage reference

6

0

Default

5

0

Default

4

0

Default

3

0

Default

2

1

1

1

0

1

Fixed voltage reference set to 4.096 V Table 5-3 FVRCON

● 100

Chapter 5 ● Project Hardware Construction ;PROG_05_01.asm LIST P=16F1823 #INCLUDE #INCLUDE RADIX DEC

; Default numbers are to base 10.

BOOK_CONFIGURATION CBLOCK 0x70 TICKS STATE_M0 STATE_M1 VALUE FLAGS ENDC

; See macro in fsm_macros.inc. ; Used to count 8.2ms ticks.

ORG 0X00 GOTO START ORG 0X04 BCF INTCON, TMR0IF INCF TICKS, F RETFIE START: MOVLB 1 SET_TMR0_CASE1 MOVLW 0x24 MOVWF TRISA CLRF TRISC BSF TRISA, 2 BSF TRISA, 5 MOVLW 0x09 MOVWF ADCON0 MOVLW 0x63

MOVWF ADCON1 SET_FREQ_32MHZ MOVLB 2 MOVLW 0x83 MOVWF FVRCON CLRF LATC COMF LATC, F BSF LATA, 4 MOVLB 3 CLRF TICKS CLRF STATE_M0 CLRF STATE_M1 BCF FLAGS, 0 SWITCH_M0 MOVF STATE_M0, W BRW GOTO M0_S0 GOTO M0_S1 GOTO M0_S2 SWITCH_M1 MOVLB 2 MOVF STATE_M1, W BRW GOTO M1_S0 GOTO M1_S1

; ; ; ;

Interrupt vector. Clear the tmr0 overflow interrupt flag. Increment by one the 8.2ms ticks. Return from interrupt.

; ; ; ; ; ; ;

Gives 8.2 ms ticks. See fsm_macros.inc 0x24 = b’00100100’. Set PORTA RA2 and RA5 as an inputs others as outputs. Set all PORTC as outputs Set RA2 (AN2) as input. Set RA5 as an input. Select channel AN2 and enable ADC.

; See page 137, left-justified result, ; conversion clock Fosc/64, ; Vref is from the internal reference 4.096 V. ; See file fsm_macros.inc.

; Enable the fixed voltage reference, and set to 4.096 V. ; See data sheet page 128.

; ; ; ;

Initialize count to zero. Initialise to 0. Initialise to 0. Initialise to low.

; Move STATE_M0 to the working register. ; Program jumps by the value moved into the working register.

; Bank 2 required for the 7 segment display driver. ; Move STATE_1 to the working register. ; Program jumps by the value moved into the working register.

;-------Machine states M0_S0 BANKSEL PORTA BTFSC PORTA, 5 ; Test the digital input for start switch activation. GOTO M0_S0_L0 ; If start switch activated jump to label M0_S0_L0. BANKSEL ADCON0 ; Bank 1 BSF ADCON0, 1 ; Start a to d conversion BTFSC ADCON0, 1 ; This and the next line loop round until the conversion is finished. GOTO $-1 MOVLW 45 ; See the text in the book, 180 is the A/D value for 4 V across the ; circuit probes.

● 101

Programming the Finite State Machine with 8-Bit PICs in Assembly and C IF_REG_GREATER_THAN_OR_EQUAL_TO_W ADRESH GOTO M0_S0_L0 GOTO SWITCH_M1 ; Here if input voltage is 4 V or less.. M0_S0_L0 CLRF VALUE ; set VALUE to 0. BSF FLAGS, 0 ; Set the update bit in FLAGS. INCF STATE_M0, F ; Here if input voltage greater than 4 V.. CLRF TICKS GOTO SWITCH_M1 M0_S1 MOVLW 91 IF_REG_GREATER_THAN_OR_EQUAL_TO_W TICKS GOTO $+2 ; Here if timed out. GOTO SWITCH_M1 ; Here if not timed out. Remain in this state. INCF VALUE, F ; Skipped here if timed out. INCF STATE_M0, F ; Now going to state 2. GOTO SWITCH_M1 M0_S2 BSF FLAGS, 0 MOVLW 10 IF_REG_GREATER_THAN_OR_EQUAL_TO_W VALUE GOTO $+4 CLRF TICKS DECF STATE_M0, F GOTO SWITCH_M1 CLRF STATE_M0 GOTO SWITCH_M1

; Here if VALUE is 10. ; Here if VALUE < 10. ; Now going back to state 1. ; Jumped to here as VALUE is 10.

SEVEN_SEG_DRIVE_MACHINE FLAGS, VALUE, M1_S0, M1_S1 END

Program 5-1 Testing the Circuit Board

● 102

Chapter 6 ● Binary Arithmetic

Chapter 6 • Binary Arithmetic 6.1 • Introduction

If you are programming in a higher-level language such as C, simple arithmetic involving addition, subtraction, multiplication, and division can seem trivial because you simply write your expression as you would on paper. The C compiler will then create the necessary machine code to do the calculation. The routines required to do the calculations can be quite complicated and if you have never done this in assembly, you could be unaware of the work you are asking the processor to do. This has to be paid for in clock cycles and memory space. This chapter is all about doing binary arithmetic in assembly language. For all of the examples in this chapter, the same circuit board from the last chapter can be used. If you haven’t built it, you can use a plugin breadboard. 6.2 • Binary Addition of Unsigned Numbers

To start with, consider unsigned numbers of eight bits, giving a range of numbers of 0 to 255. There are eight bits in a PIC register, or file as it is referred to, and usually eight bits in a byte. Table 6-1 shows four example values: 192, 195, 3, and 6. These values are expressed by summing the weighted values of each bit that is set to 1, referred to as being on or high.

Bits

7

6

5

4

3

2

1

0

Weight

128

64

32

16

8

4

2

1

1

1

0

0

0

0

0

0

1

1

0

0

0

0

1

1

0

0

0

0

0

0

1

1

0

0

0

0

0

1

1

0

Example value 192 Example value 195 Example value 3 Example value 6

Table 6-1 Example Eight-Bit Bytes

● 103

Programming the Finite State Machine with 8-Bit PICs in Assembly and C

Any value from 0 to 255 can be expressed by selecting bits. The value 192 is B’11000000’ in binary and the value 3 is B’00000011’. Adding 192 to 3 gives 195. 11000000 = 192 +00000011 = 3 11000011 = 195 Adding 3 to 6 gives 9. In any column, 1 + 0 gives 1, but 1 + 1 gives a value too large for that column. Therefore, it is carried as a 1 to the next column. In this example, the two’s column is a 2 added to a 2 giving the carry over into the four’s column. In the four’s column, the carry added to the existing four value results in an eight that is carried over to the eight’s column. 00000011 = 3 +00000110 = 6 00001001 = 9 Another example is adding 3 + 3 to get 6. In the first column, there is a carry to the two’s column giving a sum of 2 + 2 + 2. This means another carry into the four’s column with 2 left in the two’s column. Also, note that multiplying by two can be done by moving all the bits one place to the left. 00000011 = 3 +00000011 = 3 00000110 = 6 6.2.1 • Adding Two Eight Bit Positive Integers

So far all very straight forward, let’s write a program that can add two eight-bit numbers and output the result to a computer. Program 6-1 has two new instructions: •

ADDWF: this adds the contents of a file to the working register and puts the result in the destination that can be either W or back in the file. If the result is greater than 255, the destination will roll over and the carry bit C in the STATUS register is set. By rolling over, I mean that if the result was, for example, 360, the carry bit represents 256 of this, leaving 104 that would appear in the destination.



ADDWFC: this adds the contents of the working register W, to a file and the carry bit C (set or not set by a previous calculation). If the carry bit has been set, it will be added as 1 to the addition of the file and, W, the result will be placed in the destination (W or F) and the carry bit either cleared or set depending on the result.

The program has a subroutine that does the addition. The result is transmitted by the subroutine TX_SIXTEEN_BIT_NUMBER. There are two separate additions in the program: 192 + 168 and 192 + 3, however, any

● 104

Chapter 6 ● Binary Arithmetic

values in the range of 0 – 255 can be tried. Fig 6-1 shows in detail what happens in the subroutine when 192 and 168 are added.

Figure 6-1 An Example of Adding Two Eight-Bit Numbers

The code for the addition is in a subroutine so that it can be called as required. There is another subroutine that transmits the 16-bit result as two single-byte transmissions. The first byte being the high order one. There are four files used as variables. Xa and Xb are the two addends to be added together. Y0 and Y1 are the two bytes that make up the sum or the result. Y1 is the high order byte. The program is not written as a finite state machine as it’s just one loop. In the first addition, Xa is loaded with 192, and Xb 168. The add and then the transmit subroutine is called. Then Xa and Xb are loaded with 192 and 3. These are then added and transmitted. To slow things down so that the print out on the computer can be read, there is a delay of two seconds included. This is just another time-wasting loop counting 244 x 8.2 ms interrupt ticks, however, it’s all we need here.

● 105

Programming the Finite State Machine with 8-Bit PICs in Assembly and C ;PROG_06_01.asm LIST P=16F1823 #INCLUDE #INCLUDE RADIX DEC BOOK_CONFIGURATION

; Default numbers are to base 10. ; See macro in fsm_macros.inc.

CBLOCK 0x70 TICKS Xa Xb Y0 Y1 ENDC

; ; ; ; ;

Used to count 8.2ms ticks. Augend for the addition. Addend for the addition. Sum low byte for the addition. Sum high byte for the addition.

; ; ; ;

Interrupt vector. Clear the tmr0 overflow interrupt flag. Increment by one the 8.2ms ticks. Return from interrupt.

; ; ; ;

See file fsm_macros.inc. Gives 8.1 ms ticks. See fsm_macros.inc bank 3 required for EUSART macro. Set up EUSART.

ORG 0X00 GOTO START ORG 0X04 BCF INTCON, TMR0IF INCF TICKS, F RETFIE START MOVLB 1 SET_FREQ_32MHZ SET_TMR0_CASE1 MOVLB 3 SET_SERIAL_TX_16F1823 LOOP CLRF TICKS MOVLW 244 IF_REG_LESS_THAN_W TICKS GOTO $-3 MOVLW 192 MOVWF Xa MOVLW 168 MOVWF Xb CALL ADD_TWO_EIGHT_BIT_NUMBERS CALL TX_SIXTEEN_BIT_NUMBER MOVLW 192 MOVWF Xa MOVLW 3 MOVWF Xb CALL ADD_TWO_EIGHT_BIT_NUMBERS CALL TX_SIXTEEN_BIT_NUMBER GOTO LOOP ; Subroutines. ADD_TWO_EIGHT_BIT_NUMBERS CLRF Y1 MOVF Xa, W ADDWF Xb, W MOVWF Y0 CLRW ADDWFC Y1, F RETURN TX_SIXTEEN_BIT_NUMBER BANKSEL TXREG MOVF Y1, W MOVWF TXREG BTFSS TXSTA, TRMT GOTO $-1 MOVF Y0, W MOVWF TXREG BTFSS TXSTA, TRMT GOTO $-1 RETURN

; A delay of 2 seconds = 244*8.2 ms. ; We have to jump back two lines in the macro. ; Move 192 to Xa. ; Move 168 to Xb. ; Call subroutine. ; Call subroutine. ; Move 192 to Xa. ; Move 3 to Xb. ; Call subroutine. ; Call subroutine.

; ; ; ; ; ; ; ;

Y = Xa + Xb, note each X variable has one byte. The output Y has a high and low byte. Zero the high order byte. Move contents of Xa to W. Add Xb to W with result in W and the carry in STATUS C. Put the low byte of the result in Y0. Clear W, it needs to be 0 for the next line. Add W, Y1 and C with result in Y1.

; Put the high order byte in W. ; Transmit W using the EUSART. ; Loop while waiting for TRMT to go high. ; Transmit the low order byte of the result. ; Loop while waiting for TRMT to go high.

END

Program 6-1 Adding Two Eight-Bit Positive Numbers

● 106

Chapter 6 ● Binary Arithmetic

6.2.2 • Serial Read Program Command Line Arguments

The program serial_read_x, provided to read data from your PIC has some command-line arguments that can be appended to change the output, depending on what you are using the PIC for. There are four modes: • In the first with no command-line argument, the output is simply single unsigned byte values (0–255). • The second is when -h is used, the output is a signed short integer value (-32768 to +32767) that is derived from reading two consecutive bytes, the first one being the most significant known as big-endian. • The third is when -d is used, the output is a signed integer value for four bytes (-2147483648 to +2147483647). • The fourth is when -f is used, where each byte is multiplied by a real number to be used to scale the output as we did previously with the potentiometer input Program 4-3. Table 6-2 below should clarify these uses. Although the serial reader program can interpret signed integers, we are initially outputting only positive numbers from the additions. Also, there is the option -o some_filename that will log all outputs to a file of your choosing. This can be done with any of the modes.

Operating System

Program Command

Microsoft Windows

serial_reader.exe

64 Bit Linux

sudo ./serial_reader_64

[options]

[output file name]

32 Bit Linux

Sudo ./serial_reader_32 [options]

[output file name]

[options]

[output file name]

Examples below shown for Microsoft Windows Command with options

Output Format

serial_reader.exe

This will output a value from 0 to 255 for each byte, one value on each line.

serial_reader.exe -o file_name

This will output a value from 0 to 255 for each byte, one value on each line and also write the same to a file called file_name.

serial_reader.exe -h

This will output a value from -32768 to +32767 for each pair of bytes the first byte being the most significant, one value on each line.

serial_reader.exe -h -o file_ name

This will output a value from -32768 to +32767 for each pair of bytes the first byte being the most significant, one value on each line and also write the same to a file called file_name.

serial_reader.exe -d

This will output a value from -2147483648 to +2147483647 for each three bytes the first byte being the most significant, one value on each line.

serial_reader.exe -d -o file_ name

This will output a value from -2147483648 to +2147483647 one value on each line and also write the same to a file called file_name.

● 107

Programming the Finite State Machine with 8-Bit PICs in Assembly and C serial_reader.exe -f scale_ coefficient

This will output two values on each line, the first a single byte value, the second the byte value multiplied by the coefficient.

serial_reader.exe -f scale_ coefficient -o file_name

This will output two values on each line, the first a single byte value, the second the byte value multiplied by the coefficient and will also write the same to a file called file_name. Table 6-2 Using The Serial Reader Program

Figure 6-2 shows how to read the output from program 06-01 on 64 bit Linux.

Figure 6-2 Using Serial Reader

6.2.3 • Adding Two Sixteen Bit Positive Numbers

Just using eight-bit numbers is to a certain extent limiting as the largest unsigned number is 255. If we move up to sixteen bits, this gives a range from 0 – 65535 which is much more useful for practical programs. This requires two bytes for each addend and three bytes for the sum to handle the maximum result possible, adding two sixteen-bit numbers. Figure 6-3 shows how to do this. The two 16-bit numbers being added are 65000 (253x256x + 232) and 50000 (195x256 + 80) giving a sum of 115000 (1x256x256 + 193x256 + 56) requiring three bytes. The highest sum for two 65535 inputs is 131070 (1x256x256 + 255x256 + 254). This number can also be accommodated by three bytes. Program 6-2 carries this out. The comments explain what each line does. The serial reader program reads a 16-bit integer with the command line option -h. The next longest type is the 32-bit (4 bytes) integer. The transmit subroutine in the PIC will have to output 4 bytes. The highest-order byte will always be zero when adding only two sixteen-bit addends.

● 108

Chapter 6 ● Binary Arithmetic

Figure 6-3 An Example of Adding Two Sixteen-Bit Numbers ;PROG_06_02.asm LIST P=16F1823 #INCLUDE #INCLUDE RADIX DEC BOOK_CONFIGURATION CBLOCK 0x70 TICKS Xa0 Xa1 Xb0 Xb1 Y0 Y1 Y2 Y3 ENDC

; Default numbers are to base 10. ; See macro in fsm_macros.inc.

; ; ; ; ; ;

Used to count 8.2ms ticks. Augend low byte. Augend high byte. Addend low byte. Addend high byte. Sum lowest byte

; Sum highest byte

ORG 0X00 GOTO START ORG 0X04 BCF INTCON, TMR0IF INCF TICKS, F RETFIE START MOVLB 1 SET_FREQ_32MHZ SET_TMR0_CASE1 MOVLB 3 SET_SERIAL_TX_16F1823

; ; ; ;

Interrupt vector. Clear the tmr0 overflow interrupt flag. Increment by one the 8.2ms ticks. Return from interrupt.

; See file fsm_macros.inc. ; Gives 8.1 ms ticks. See fsm_macros.inc ; Set up EUSART.

● 109

Programming the Finite State Machine with 8-Bit PICs in Assembly and C LOOP CLRF TICKS MOVLW 244 IF_REG_LESS_THAN_W TICKS GOTO $-3 ; Loading value int Xa 256*253 + 232 = 65000 MOVLW 253 MOVWF Xa1 MOVLW 232 MOVWF Xa0 ; Loading value into Xb 256*195 + 80 = 50000 MOVLW 195 MOVWF Xb1 MOVLW 80 MOVWF Xb0 CALL ADD_TWO_SIXTEEEN_BIT_NUMBERS CALL TX_THIRTY_TWO_BIT_NUMBER GOTO LOOP

; A delay of 2 seconds = 244*8.2 ms. ; We have to jump back two lines in the macro. ; ; ; ;

This Move This Move

can 253 can 232

be to be to

expressed as MOVLW HIGH 65000. Xa0. expressed as MOVLW LOW 65000. Xa1.

; ; ; ; ; ;

This Move This Move Call Call

can be expressed as MOVLW HIGH 50000. 195 to Xb0. can be expressed as MOVLW LOW 50000. 80 to Xb1. subroutine. subroutine.

; Subroutines. ADD_TWO_SIXTEEEN_BIT_NUMBERS ; Y = Xa + Xb, note each variable has a high and low byte. CLRF Y3 ; Clear most significant result byte. This will always be zero when ; adding two 16 bit unsigned integers. CLRF Y2 ; Clear high middle result byte. MOVFW Xa0 ; Move Xa low byte to W. ADDWF Xb0, W ; Add Xa and Xb low bytes with result in W and STATUS C. MOVWF Y0 ; Move the result to Y3, the lowest output byte. MOVFW Xa1 ; Move the Xb high byte to W. ADDWFC Xb1, W ; Add Xa and Xb high bytes and C, put the result in W and STATUS C. MOVWF Y1 ; Move the result to Y2, the low middle output byte. CLRW ; Clear w. ADDWFC Y2, F ; Add the carry, Y0 and W with result in Y1. Note prior to ; the addition Y0 an w are zero. If there is no carry nothing ; will be added. RETURN TX_THIRTY_TWO_BIT_NUMBER BANKSEL TXREG MOVF Y3, W MOVWF TXREG BTFSS TXSTA, TRMT GOTO $-1 MOVF Y2, W MOVWF TXREG BTFSS TXSTA, TRMT GOTO $-1 MOVF Y1, W MOVWF TXREG BTFSS TXSTA, TRMT GOTO $-1 MOVF Y0, W MOVWF TXREG BTFSS TXSTA, TRMT GOTO $-1 RETURN

; There are 4 bytes because the serial reader program reads either 1, ; 2 or 4 bytes and we need 3. ; Put the highest order byte in W. ; Transmit W using the EUSART.

; Put the high middle-order byte in W. ; Transmit W using the EUSART.

; Put the low middle-order byte in W. ; Transmit W using the EUSART.

; Put the lowest-order byte in W. ; Transmit W using the EUSART.

END

Program 6-2 Adding Two Sixteen-Bit Numbers

Figure 6-4 shows the serial reader program being used to read the 32-bit output results on Linux 64. First the -d option is used to read 32-bit integers (four bytes). The default with no option to read the four separate bytes showing the first one being 0 the next is 1 then 193 then 56, multiplying each by the byte values in the word gives the following: 1 x 256 x 256 + 193 x 256 + 56 = 115000

● 110

Chapter 6 ● Binary Arithmetic

Figure 6-4 Using Serial Reader

6.3 • Binary Subtraction of unsigned integers

Subtracting numbers is similar. The next program subtracts one sixteen-bit number from another. As an example, we will subtract 1000 from 1500. Both numbers are greater than 255 so they require two bytes each. 1500 is 256x5 + 220 and 1000 is 256x3 + 232. The subtraction is outlined below in Table 6-3 with explanation of what has to happen with the assembly code.

High Order Byte counts the 256s

Low order Byte counts the units

Decimal Values

5

220 (Need to borrow from 256s, this makes C the not borrow bit = 0)

1500

3

232

1000

5 - 3 – C = 1, C is 0 because of the borrow, therefore C is 1 Uses the SUBWFB instruction.

220 + 256(borrow) – 232 = 244 Uses the SUBWF instruction.

500

Table 6-3 Example Sixteen-bit Subtraction

Something confusing to follow, but doesn’t cause a problem in practice, is the SUBWFB instruction, which is the subtract W from file with borrow used when subtracting the higherorder bytes in the multibyte subtraction. In a multibyte subtraction, if a subtraction has occurred where the result is either zero or positive, the carry bit in the STATUS register is set to 1. This is taken as meaning that no borrow was required. If the result of the subtraction is negative, the carry bit in the STATUS register is cleared to 0 meaning that a borrow was required. What happens next is that in the subtraction involving the next higher pair of bytes using SUBWFB, the state of the carry bit is taken into account. The working register W is subtracted from F. If C is 0, another 1 is subtracted. If C is 1, no extra 1 is subtracted. You don’t need to do anything other than use the instruction. This can be stated

● 111

Programming the Finite State Machine with 8-Bit PICs in Assembly and C

as follows: F – W – C -> destination (W or F) (also, the carry bit C is affected)

Program 6-3 demonstrates this with Figure 6-5 showing its output. As the output is a 16-bit word, the -h option is used. ;PROG_06_03.asm LIST P=16F1823 #INCLUDE #INCLUDE RADIX DEC BOOK_CONFIGURATION

; Default numbers are to base 10. ; See macro in fsm_macros.inc.

CBLOCK 0x70 TICKS Xa0 Xa1 Xb0 Xb1 Y0 Y1 ENDC

; ; ; ; ; ; ;

Used to count 8.2ms ticks. Minuend low byte. Minuend high byte. Subtrahend low byte. Subtrahend high byte. Difference lowest byte. Difference highest byte.

; ; ; ; ; ; ; ; ; ; ;

Interrupt vector. Clear the tmr0 overflow interrupt flag. Increment by one the 8.2ms ticks. Return from interrupt.

ORG 0X00 GOTO START ORG 0X04 BCF INTCON, TMR0IF INCF TICKS, F RETFIE START MOVLB 1 SET_FREQ_32MHZ SET_TMR0_CASE1 MOVLB 3 SET_SERIAL_TX_16F1823

See file fsm_macros.inc. Gives 8.1 ms ticks. See fsm_macros.inc Set up EUSART.

LOOP CLRF TICKS MOVLW 244 IF_REG_LESS_THAN_W TICKS GOTO $-3 ; Loading value int Xa 256*5 + 220 = 1500 MOVLW 5 MOVWF Xa1 MOVLW 220 MOVWF Xa0 ; Loading value into Xb 256*3 + 232 = 1000 MOVLW 3 MOVWF Xb1 MOVLW 232 MOVWF Xb0 CALL SUB_SIXTEEEN_BIT_NUMBERS CALL TX_SIXTEEN_BIT_NUMBER GOTO LOOP ; Subroutines. SUB_SIXTEEEN_BIT_NUMBERS CLRF Y1 MOVF Xb0, W SUBWF Xa0, W

; A delay of 2 seconds = 244*8.2 ms. ; We have to jump back two lines in the macro. ; ; ; ;

This Move This Move

can be expressed as MOVLW HIGH 1500 5 to Xa0. This is the high byte of 1500. can be expressed as MOVLW LOW 1500 220 to Xa1. This is the low byte of 1500.

; ; ; ; ; ;

This Move This Move Call Call

can be expressed as MOVLW HIGH 1000 3 to Xb0. This is the high byte of 1000. can be expressed as MOVLW LOW 1000. 232 to Xb1. This is the low byte of 1000. subroutine. subroutine.

MOVWF Y0

; ; ; ; ; ; ; ;

MOVF Xb1, W

; Move the Xb high byte to W.

● 112

Y = Xa - Xb , note each variable has a high and low byte. Clear most significant result byte. Move Xb low byte to W. Subtract W (Xb1) from Xa1, result in W. If the result is not negative there is no need for a borrow so the carry bit C in STATUS is set to high. C is also referred to as ‘not borrow’. Move low byte result to Y1.

Chapter 6 ● Binary Arithmetic SUBWFB Xa1, W MOVWF Y1 RETURN TX_SIXTEEN_BIT_NUMBER BANKSEL TXREG MOVF Y1, W MOVWF TXREG BTFSS TXSTA, TRMT GOTO $-1 MOVF Y0, W MOVWF TXREG BTFSS TXSTA, TRMT GOTO $-1 RETURN

; Subtract the ‘not borrow’ and W (Xb0) from Xa0 put the ; result in W. ; Move the result to Y1, the high output byte.

; Put the high order byte in W. ; Transmit W using the EUSART. ; Loop while waiting for TRMT to go high. ; Transmit the low order byte of the result. ; Loop while waiting for TRMT to go high.

END

Program 6-3 Subtracting a Sixteen-Bit Number from Another

Figure 6-5 Using Serial Reader

6.4 • Binary Subtraction with Negative Results

In the previous example, although the low order bytes gave a negative result, it was handled by borrowing from the high order bytes using the carry/not borrow bit in the STATUS register. If the high order byte subtraction has a negative result, how is this handled? I’ll start with a demonstration using the same program but modifying it so that 1500 is subtracted from 1000 to give an answer of -500. This is Program 6-4, but the only difference is the input values to the subtraction shown here. To save space, only the modified part is in the book. The full program is in the file. LOOP CLRF TICKS MOVLW 244 IF_REG_LESS_THAN_W TICKS GOTO $-3 ; Loading value into Xb 256*3 + 232 = 1000 MOVLW 3 MOVWF Xa1 MOVLW 232 MOVWF Xa0 ; Loading value int Xa 256*5 + 220 = 1500 MOVLW 5 MOVWF Xb1 MOVLW 220 MOVWF Xb0 CALL SUB_SIXTEEEN_BIT_NUMBERS CALL TX_SIXTEEN_BIT_NUMBER GOTO LOOP

; A delay of 2 seconds = 244*8.2 ms. ; We have to jump back two lines in the macro. ; ; ; ;

This Move This Move

can be expressed as MOVLW HIGH 1000 3 to Xa0. This is the high byte of 1000. can be expressed as MOVLW LOW 1000 232 to Xa1. This is the low byte of 1000.

; ; ; ; ; ;

This Move This Move Call Call

can be expressed as MOVLW HIGH 1500 5 to Xb0. This is the high byte of 1500. can be expressed as MOVLW LOW 1000. 220 to Xb1. This is the low byte of 1500. subroutine. subroutine.

Program 6-4 Excerpt

● 113

Programming the Finite State Machine with 8-Bit PICs in Assembly and C

Figure 6-6 Using Serial Reader

As can be seen in Figure 6-4, the serial reader correctly displays the output of 1000, minus 1500 as -500 when the option for a short integer -h (two bytes) is used. So how are negative numbers represented in binary? 6.5 • Negative numbers in binary

When we do arithmetic on paper to represent a negative number, we just put a – sign in front of the number and take it into account when doing additions and subtractions. Binary arithmetic in computing is somewhat different. Let us start by considering an integer data type of only 4 bits. This means that unsigned numbers between 0 and 15 can be represented. Table 6-4 shows the bit pattern but there are two value columns: one for unsigned and one for how signed is represented. Note how the same bit pattern is used for 0 to +15 unsigned as -8 to +7 signed. For 0 to +7, the two values for signed and unsigned are the same, but the +8 to +15 range of the unsigned maps across to the -8 to -1 of the signed. Now take +3 that is 0011 and take -3 from the signed integers that is 1101. If you add +3 and -3 you get 0. Let’s add the two binary numbers 0011 and 1101. 0011 = +3 1101 = -3 0000 with 1 carried out. You can try any pair of positive and negative numbers from the table that have the same absolute value. For example, +2 and -2. The sum of all of these binary numbers will result in 0. Now if for example, we add +3 and -8 the answer is -5. Using our binary numbers from the table 0011 and 1000: 0011 = +3 1000 = -8 1011 = -5 Giving the correct answer of -5 from the table.

● 114

Chapter 6 ● Binary Arithmetic Bits

Value

8

4

2

1

Unsigned

Signed

0

0

0

0

0

+0

0

0

0

1

+1

+1

0

0

1

0

+2

+2

0

0

1

1

+3

+3

0

1

0

0

+4

+4

0

1

0

1

+5

+5

0

1

1

0

+6

+6

0

1

1

1

+7

+7

1

0

0

0

+8

-8

1

0

0

1

+9

-7

1

0

1

0

+10

-6

1

0

1

1

+11

-5

1

1

0

0

+12

-4

1

1

0

1

+13

-3

1

1

1

0

+14

-2

1

1

1

+15

-1

1

Table 6-4 Bit Pattern Used for Four-Bit Integers Signed and Unsigned

This way of representing negative numbers is called two’s complement. As we have demonstrated, the two’s complement of a number when added to that number results in zero. This means that the two’s complement of a number is it’s negative. So how do we work out what the two’s complement of a number is? Let’s start by taking any binary number from the table in the positive range say 5 = 0101. If we invert the bits, that is, to make zeros ones and ones zeros, you get 1010. Adding these numbers gives: 0101 1010 1111 It should be seen that this will apply to all binary numbers. If you add the number to the number obtained by inverting the bits, the sum will contain all 1s. The number that is the bit inversion of the original number is called one’s complement. What happens if you add 1 to the one’s complement? 1111 0001 0000 with 1 carried out By adding 1 to the ones’s complement, you get the two’s complement that is the negative

● 115

Programming the Finite State Machine with 8-Bit PICs in Assembly and C

value. Take the value 4: in our four-bit integer type 0100, the one’s complement is 1011. Adding 1 to that gives 1100, appearing in the table as -4. Also note that this is reversible if we take -4 represented by 1100 and take its one’s complement, 0011, and then add 1. We get 0100 which is +4. Computers do subtraction by adding the two’s complement of the number that is being subtracted (the subtrahend). For a demonstration, I have used an integer type of only four bits. A point to remember here is that you have to know the width in bits of the integer. You can have • Signed bytes that range from -128 to + 127, • Signed short integers, usually, 16 bits ranging from -32768 to + 32767 and • Signed integers, usually 32 bits that range from -2147483648 to +2147483647. With 64 bit long integers that are huge, every additional bit doubles the size. You will also notice that with a signed integer, the most significant bit is 0 when it is positive and 1 when it is negative. If negative, remember that it’s the two’s complement. How do you know if a binary number is a signed integer or an unsigned integer or some other data type? Well, you don’t just by looking at it. This is why variables have to be declared in C and other statically typed languages, the declaration states the data type so the compiler knows what it is dealing with. 6.6 • Binary Multiplication

PICs do not have a handy multiplication instruction. Therefore we have to write our own routine. If you take a binary number and shift the bits one place to the left, it doubles the number. It is possible to multiply by values other than two by combining addition and multiplying by powers of two. As an example, if I want to multiply variable X by 35, I could take a copy of X and shift the bits 5 places left. Each place shifted multiplies by 2 giving a multiplication by 32. If I take another copy of X and move the bits one place to the left, I multiply by 2. Finally, if I take another copy of X and add it to the 32 times X and the 2 times X, I will have 35 times X. X + 2X + 32X = 35X

This method executes very quickly but isn’t the method I am going to use here. The method of multiplication I am going to describe is not the fastest but is easy to implement and is versatile. It is simply a repetitive addition. If I want to multiply X by 35, I am going to just add X to the output 35 times. We will end up with a multiplying subroutine that accepts two 16-bit unsigned integer variables to multiply together. When I tested this method it took approximately 75 ms to multiply 46340 by 46341 to give a result of 2,147,441,940. This is the worst case we can use without overflowing a 32-bit signed integer’s positive range. It will multiply 65535 times 65535 in approximately 107 ms but the result is a negative

● 116

Chapter 6 ● Binary Arithmetic

number because of the overflow. The subroutine that implements multiplication is written as a finite state machine. Figure 6-5 shows its state diagram. This implementation is only for positive integers.

Figure 6-7 State Diagram for the Multiplication Subroutine

This state machine is only for the subroutine that multiplies two sixteen-bit variables, Xa and Xb, and puts the product in variable Y. Xa is made up of Xa1 the higher byte, and Xa0 the lower byte, similarly Xb has Xb1 and Xb0. The result, or product Y is in Y3 (highest byte), Y2, Y1 and Y0. The state machine has 2 states on calling the subroutine. State 0 is entered, the output files Y are zeroed and Xa1 is loaded into BUFFERH and Xa0 is loaded into BUFFERL. These buffers are used as counters to count the number of times Xb gets added to the output files Y. We could have directly used the Xa files but this would have lost the value that might be needed again when the subroutine is used in another application. If Xb is zero, the routine immediately returns as otherwise, S1 would loop around Xa times adding zero each time. If however, Xa is zero, the routine returns immediately from state 1. The complete program is provided below. Figure 6-8 is the output from the serial reader. One thing that might require explanation is the use of the MOVF instruction to move a file with itself as the destination rather than working register W. This might seem pointless, but it sets the Z bit in the STATUS register to 1 if the file is zero and unsets it to 0 if it's anything other than zero.

● 117

Programming the Finite State Machine with 8-Bit PICs in Assembly and C ;PROG_06_05.asm LIST P=16F1823 #INCLUDE #INCLUDE RADIX DEC BOOK_CONFIGURATION

; Default numbers are to base 10. ; See macro in fsm_macros.inc.

CBLOCK 0x70 TICKS Xa0 Xa1 Xb0 Xb1 Y0 Y1 Y2 Y3 BUFFH BUFFL ENDC

; Used to count 8.2ms ticks. ; Factor low byte. ; Factor high byte. ; Factor low byte. ; Factor high byte. ; Product lowest byte

; Product highest byte

ORG 0X00 GOTO START ORG 0X04 BCF INTCON, TMR0IF INCF TICKS, F RETFIE

; ; ; ; ; ; ; ; ; ; ;

START MOVLB 1 SET_FREQ_32MHZ SET_TMR0_CASE1 MOVLB 3 SET_SERIAL_TX_16F1823

Interrupt vector. Clear the tmr0 overflow interrupt flag. Increment by one the 8.2ms ticks. Return from interrupt.

See file fsm_macros.inc. Gives 8.1 ms ticks. See fsm_macros.inc Set up EUSART.

LOOP CLRF TICKS MOVLW 244 IF_REG_LESS_THAN_W TICKS GOTO $-3

; A delay of 2 seconds = 244*8.2 ms. ; We have to jump back two lines in the macro.

; Loading value int Xa MOVLW HIGH 46340 MOVWF Xa1 MOVLW LOW 46340 MOVWF Xa0 ; Loading value into Xb MOVLW HIGH 46341 MOVWF Xb1 MOVLW LOW 46341 MOVWF Xb0 CALL MULT_TWO_SIXTEEN_BIT_NUMBERS CALL TX_THIRTY_TWO_BIT_NUMBER GOTO LOOP ; Subroutines. MULT_TWO_SIXTEEN_BIT_NUMBERS MULT_S0 CLRF Y3 ; CLRF Y2 ; CLRF Y1 ; CLRF Y0 ; MOVF Xb1, F ; BTFSS STATUS, Z ; GOTO $+4 ; MOVF Xb0, F ; BTFSC STATUS, Z ; RETURN ; MOVF Xa1, W MOVWF BUFFH ; MOVF Xa0, W MOVWF BUFFL ; MULT_S1 MOVF BUFFH, F BTFSS STATUS, Z

● 118

; Move 80 to Xb1. ; Call subroutine. ; Call subroutine.

; Y = Xa*Xb, note each variable has a high and low byte. Clear output to zero. Here because Xb0 is not zero. Clear output to zero. Clear output to zero. Clear output to zero. By moving a file to itself the Z flag in STATUS gets affected. Test Xb1. Here because Xb1 is not zero. Here because Xb1 is zero. Test Xa0. Here because Xb1 and Xa0 are both zero. Move Xa1 to BUFFH. Move Xa0 to BUFFL.

; By moving a file to itself the Z flag in STATUS gets affected. ; Test BUFFH.

Chapter 6 ● Binary Arithmetic GOTO $+4 ; MOVF BUFFL, F BTFSC STATUS, Z ; RETURN ; ADD_REGISTERS_16 Y1, Y0, MOVLW 1 SUBWF BUFFL, F CLRW SUBWFB BUFFH, F GOTO MULT_S1 TX_THIRTY_TWO_BIT_NUMBER BANKSEL TXREG MOVF Y3, W MOVWF TXREG BTFSS TXSTA, TRMT GOTO $-1 MOVF Y2, W MOVWF TXREG BTFSS TXSTA, TRMT GOTO $-1 MOVF Y1, W MOVWF TXREG BTFSS TXSTA, TRMT GOTO $-1 MOVF Y0, W MOVWF TXREG BTFSS TXSTA, TRMT GOTO $-1 RETURN

Here because BUFFH is not zero. ; Here because BUFFH is zero. Test BUFFL. Here because BUFFH and BUFFL are both zero. Xb1, Xb0, Y3, Y2, Y1, Y0

; There is 4 bytes because the serial reader program reads either 1, ; 2 or 4 bytes and we need 3. ; Put the highest order byte in W. ; Transmit W using the EUSART.

; Put the high middle order byte in W. ; Transmit W using the EUSART.

; Put the low middle order byte in W. ; Transmit W using the EUSART.

; Put the lowest order byte in W. ; Transmit W using the EUSART.

END

Program 6-5 Multiplying Two Sixteen-Bit Integers

Figure 6-8 Using Serial Reader 6.7 • Binary Division

For binary division, I’ll use a similar technique of repetitive subtraction. This will be integer division where the result is the integer below the real value. For example, dividing 35 by 4 gives the result in the set of real numbers of 8.75, with integer division the result will be 8. Taking this example to divide 35 by 4. 35 – 4 – 4 - 4 - 4 – 4 – 4 - 4 - 4 = 3 This shows that 4 divides into 32 8 times with 3 left over. All we have to do for division is to subtract the divisor over and over counting the number of times it can be subtracted.

● 119

Programming the Finite State Machine with 8-Bit PICs in Assembly and C

Again the state machine is only for the subroutine that divides one sixteen-bit unsigned integer into another. Xa is the numerator or dividend and Xb is the denominator or divisor, Y is the quotient. Xa is made up of Xa1 and Xa0 and Xb is made up of Xb1 and Xb0. Y is made up of Y1 and Y0. The quotient will only need a 16-bit integer. The remainder after the division will be in Xc. To preserve the value of Xa if it is needed again in the program, it gets transferred to BUFF made up of BUFFH and BUFFL. Xb is then subtracted from BUFF on each loop. State 0 transfers Xa to BUFF and resets Y and Xc to zero. The resetting of Y and Xc does not look like a Mealy machine as it happens before the start of the transitions but has been done this way to shorten the code. The transitions from state 0 depend on if Xb is zero or not. If it is, the subroutine returns as division by Xb would result in the subtraction loop running forever, division by zero! If Xb is greater than zero, the transition is to state 1. In state 1, Xb is subtracted from BUFF and the result placed in BUFF until BUFF is less than zero. If the result is less than zero, the subroutine returns. If it isn't, Y is incremented and the loop repeated. This results in Y holding the quotient of the integer division and Xc holding the remainder. The remainder is the value of BUFF before the last subtraction. It is stored in Xc. Figure 6-9 shows the state diagram. Program 6-6 is the program. This listing shows 65535 being divided by 5997 resulting in 11 with a remainder of 8. Figure 6-10 shows the output from serial reader using the option -h to read a short (16-bit integer).

Figure 6-9 State Diagram for the Division Subroutine

● 120

Chapter 6 ● Binary Arithmetic ;PROG_06_06.asm LIST P=16F1823 #INCLUDE #INCLUDE RADIX DEC BOOK_CONFIGURATION CBLOCK 0x70 TICKS Xa0 Xa1 Xb0 Xb1 Xc0 Xc1 Y0 Y1 BUFFH BUFFL FLAGS ENDC

; Default numbers are to base 10. ; See macro in fsm_macros.inc.

; ; ; ; ; ; ; ; ;

Used to count 8.2ms ticks. Dividend low byte. Dividend high byte. Divisor low byte. Divisor high byte. Remainder low byte. Remainder high byte. Quotient low byte. Quotient high byte.

; FLAGS bit 1 will be used to signify end of division.

ORG 0X00 GOTO START ORG 0X04 BCF INTCON, TMR0IF INCF TICKS, F RETFIE START MOVLB 1 SET_FREQ_32MHZ SET_TMR0_CASE1 MOVLB 3 SET_SERIAL_TX_16F1823

; ; ; ; ; ; ; ; ; ; ;

Interrupt vector. Clear the tmr0 overflow interrupt flag. Increment by one the 8.2ms ticks. Return from interrupt.

See file fsm_macros.inc. Gives 8.1 ms ticks. See fsm_macros.inc Set up EUSART.

LOOP CLRF TICKS MOVLW 244 IF_REG_LESS_THAN_W TICKS GOTO $-3

; A delay of 2 seconds = 244*8.2 ms. ; We have to jump back two lines in the macro.

; Loading value int Xa MOVLW HIGH 65535 MOVWF Xa1 MOVLW LOW 65535 MOVWF Xa0 ; Loading value into Xb MOVLW HIGH 5957 MOVWF Xb1 MOVLW LOW 5957 MOVWF Xb0 CALL DIV_TWO_SIXTEEN_BIT_NUMBERS BANKSEL TXREG CALL TX_SIXTEEN_BIT_NUMBER MOVF Xc1, W MOVWF Y1 MOVF Xc0, W MOVWF Y0 CALL TX_SIXTEEN_BIT_NUMBER GOTO LOOP ; Subroutines. DIV_TWO_SIXTEEN_BIT_NUMBERS ; Y DIV_S0 CLRF Y0 CLRF Y1 CLRF Xc1 CLRF Xc0 MOVF Xb1, F BTFSS STATUS, Z GOTO $+4 ; Here MOVF Xb0, F ; Here BTFSC STATUS, Z RETURN ; Here

; Call subroutine. ; Call subroutine. Transmit the quotient.

; Call subroutine.

Transmit the remainder..

= Xa/Xb, note each variable has a high and low byte.

because Xb1 is not 0. because Xb1 is 0. because Xb0 is 0. Also Xb1 is 0.

● 121

Programming the Finite State Machine with 8-Bit PICs in Assembly and C MOVF Xa1, W MOVWF BUFFH MOVF Xa0, W MOVWF BUFFL BCF FLAGS, 1 DIV_S1 BTFSC FLAGS, 1 RETURN MOVF BUFFH, W MOVWF Xc1 MOVF BUFFL, W MOVWF Xc0 MOVF Xb0, W SUBWF BUFFL, F MOVF Xb1, W SUBWFB BUFFH, F BTFSS STATUS, C BSF FLAGS, 1 DIV_S2 BTFSC FLAGS, 1 GOTO DIV_S1 INCF Y0, F BTFSC STATUS, Z INCF Y1, F GOTO DIV_S1 TX_SIXTEEN_BIT_NUMBER BANKSEL TXREG MOVF Y1, W MOVWF TXREG BTFSS TXSTA, TRMT GOTO $-1 MOVF Y0, W MOVWF TXREG BTFSS TXSTA, TRMT GOTO $-1 RETURN

; Load Xa high byte to BUFFH. ; Load Xa high byte to BUFFH. ; Ensure end of division flag low.

; Here because end of division flag set. ; Xc will contain the remainder.

; Subrtact Xb1 from BUFFL result in BUFFL. ; Subrtact Xb0 from BUFFH result in BUFFH. ; Here because C == 0 therefore below zero.

; ; ; ; ;

Because BUFF is now less then Xb this is the last loop and Xc contains the remainder. Counting the number of times Xb has been subtracted from BUFF with a positive rersult. If Y0 rolls over to 0, Y1 has to be incremented with the carry.

; Put the low order byte in W. ; Transmit W using the EUSART. ; Loop while waiting for TRMT to go high. ; Transmit the high order byte of the result. ; Loop while waiting for TRMT to go high.

END

Program 6-6 Integer Division - Two Sixteen-Bit Numbers

Figure 6-10 Using Serial Reader

Finally, a similar program to divide a 16-bit number into a 24-bit number. This will be required for the scaling of the raw result from the analog to digital converter in the voltmeter. The method is the same in the previous program except the numerator has an extra 8 bits.

● 122

Chapter 6 ● Binary Arithmetic ;PROG_06_07.asm LIST P=16F1823 #INCLUDE #INCLUDE RADIX DEC BOOK_CONFIGURATION CBLOCK 0x70 TICKS Y0 Y1 Y2 Y3 FLAGS ENDC CBLOCK 0x20 Xa0 Xa1 Xa2 Xb0 Xb1 Xc0 Xc1 Xc2 BUFFL BUFFM BUFFH ENDC

; Default numbers are to base 10. ; See macro in fsm_macros.inc.

; Used to count 8.2ms ticks. ; Quotient lowest byte.

; Quotient highest byte.

; Dividend low byte. ; Dividend high byte. ; ; ; ;

Divisor low byte. Divisor high byte. Remainder low byte. Remainder high byte.

; ; ; ; ; ; ; ; ; ; ;

Interrupt vector. Clear the tmr0 overflow interrupt flag. Increment by one the 8.2ms ticks. Return from interrupt.

ORG 0X00 GOTO START ORG 0X04 BCF INTCON, TMR0IF INCF TICKS, F RETFIE START MOVLB 1 SET_FREQ_32MHZ SET_TMR0_CASE1 MOVLB 3 SET_SERIAL_TX_16F1823

See file fsm_macros.inc. Gives 8.1 ms ticks. See fsm_macros.inc Set up EUSART.

LOOP MOVLB 0 CLRF TICKS MOVLW 244 IF_REG_LESS_THAN_W TICKS GOTO $-3 ; Loading value int Xa MOVLW 0xFF MOVWF Xa2 MOVLW 0xFF MOVWF Xa1 MOVLW 0xFF MOVWF Xa0 ; Loading value into Xb MOVLW HIGH 5957 MOVWF Xb1 MOVLW LOW 5957 MOVWF Xb0 CALL DIV_24_BY_16_BIT

; A delay of 2 seconds = 244*8.2 ms. ; We have to jump back two lines in the macro. ; 0xFFFFFF is 16,777,215 in decimal

; 5957 in decimal

; Call subroutine, returns with quotient in

CALL TX_THIRTY_TWO_BIT_NUMBER MOVLB 0 CLRF Y3 MOVF Xc2, W MOVWF Y2 MOVF Xc1, W MOVWF Y1 MOVF Xc0, W MOVWF Y0

Y2, Y1 and Y0.

; Call subroutine. Transmit the quotient. ; Y3 not used for a 24 bit number.

● 123

Programming the Finite State Machine with 8-Bit PICs in Assembly and C CALL TX_THIRTY_TWO_BIT_NUMBER GOTO LOOP

; Call subroutine.

Transmit the remainder..

; Subroutines. DIV_24_BY_16_BIT ; Y = Xa/Xb, DIV_S0 CLRF Y0 ; Set Y to zero. CLRF Y1 CLRF Xc1 ; Set Xc to zero. CLRF Xc0 MOVF Xb1, F BTFSS STATUS, Z GOTO $+4 ; Here because Xb1 is not 0. MOVF Xb0, F ; Here because Xb1 is 0. BTFSC STATUS, Z RETURN ; Here because Xb0 is 0. Also Xb1 is 0. MOVF Xa2, W MOVWF BUFFH ; Load Xa high byte to BUFFH. MOVF Xa1, W MOVWF BUFFM ; Load Xa high byte to BUFFM. MOVF Xa0, W MOVWF BUFFL ; Load Xa high byte to BUFFM. BCF FLAGS, 1 ; Ensure end of division flag low. DIV_S1 BTFSC FLAGS, 1 RETURN MOVF BUFFH, W MOVWF Xc2 MOVF BUFFM, W MOVWF Xc1 MOVF BUFFL, W MOVWF Xc0 MOVF Xb0, W SUBWF BUFFL, F MOVF Xb1, W SUBWFB BUFFM, F CLRW SUBWFB BUFFH, F BTFSS STATUS, C BSF FLAGS, 1

; Here because end of division flag set. ; Xc will contain the remainder. ; Xc will contain the remainder. ; Xc will contain the remainder. ; Subrtact Xb0 from BUFFL result in BUFFL. ; Subrtact Xb1 from BUFFM result in BUFFM. ; Subtract the carry if there is one.

DIV_S2 BTFSC FLAGS, 1 GOTO DIV_S1

; Here because C == 0 therefore below zero.

INCF Y0, F BTFSC STATUS, Z INCF Y1, F GOTO DIV_S1 TX_THIRTY_TWO_BIT_NUMBER BANKSEL TXREG MOVF Y3, W MOVWF TXREG BTFSS TXSTA, TRMT GOTO $-1 MOVF Y2, W MOVWF TXREG BTFSS TXSTA, TRMT GOTO $-1 MOVF Y1, W MOVWF TXREG BTFSS TXSTA, TRMT GOTO $-1 MOVF Y0, W MOVWF TXREG BTFSS TXSTA, TRMT GOTO $-1 RETURN

; ; ; ; ;

Because BUFF is now less then Xb this is the last loop and Xc contains the remainder. Counting the number of times Xb has been subtracted from BUFF with a positive rersult. If Y0 rolls over to 0, Y1 has to be incremented with the carry.

; There is 4 bytes because the serial reader program reads either 1, ; 2 or 4 bytes and we need 3. ; Put the highest order byte in W. ; Transmit W using the EUSART.

; Put the high middle order byte in W. ; Transmit W using the EUSART.

; Put the low middle order byte in W. ; Transmit W using the EUSART.

; Put the lowest order byte in W. ; Transmit W using the EUSART.

END

Program 6-7 Integer Division of a 24-bit Number by a Sixteen-Bit Number

● 124

Chapter 6 ● Binary Arithmetic

The program divides 16,777,215 by 5,957. This gives 2816 with a remainder of 2303. Figure 6-11 shows the output of the program, first outputting the quotient and then the remainder as 32-bit integers, hence the use of the -d option.

Figure 6-11 Using Serial Reader

● 125

Programming the Finite State Machine with 8-Bit PICs in Assembly and C

Chapter 7 • Digital Voltmeter Project 7.1 • Introduction

Chapter 5 was a guide to constructing the hardware part of the project. The circuit was tested using Program 5-1. This chapter will develop a program to use this circuit as a digital voltmeter. This is the most complicated program so far. It involves sequencing of the display digits, analog measurement, sleep mode to conserve battery life, and the binary arithmetic dealt with in chapter 6. Voltage is read using a single-digit display which displays four significant figures by flashing four figures for each reading in-turn. Each figure will appear for 1/2 second with a blank space of 1/4 second between figures and a blank space of 1 second before the next reading. This means a reading of 7.25 volts will appear as ‘0’, ‘7’, ‘2’, ‘5’, following on with a onesecond delay before the next reading. There are three state machines in the program: • Sequence handler • The seven-segment display driver • Voltage reading and digit value handler. This project introduces the sleep mode that allows the PIC to conserve battery charge when no voltage is sensed by the probes. 7.2 • The State Diagrams

The three machines are shown in figures Fig 7-1 and Fig 7-2. The machines are separate but share processor and memory space. As this is a more complicated program, I will recap on how several machines run together. Each machine is made up of several states. The machine will stay in its current state until a condition is true that causes it to change to another state. This condition can be an input of some sort such as an actual digital input on a pin or the comparison of a counter value with some constant or a logical combination of these. If a machine remains in a particular state, it does not mean that the code being executed is only the local lines of code for that state. After executing local lines, the program goes to the switch block that uses the BRW for the next machine that has a turn of the processor to go to the relevant state for that machine. This way all the machines take a turn in executing their current state code. In the voltmeter program, the order the machines take is M0, M2, M1 and back to M0. The reason for this order is because the macro for the seven-segment driver goes to M0 next. Figure 4-14 gives an overview of how machine rotation works for two machines. In this project, we have three. Below are state diagrams and program listings. This program uses variable FLAGS as a set of booleans that are used as signals. Bit 0 is the update display signal, bit 1 is the division complete signal used in binary arithmetic, bit 2 is the voltage reading available, meaning that probe voltage measured is above 111 mV, and bit 3 is the signal to tell machine 2 to read voltage. Although there are only two states in machine 2, they allow the machine to

● 126

Chapter 7 ● Digital Voltmeter Project

idle in state 0 until a reading is required, thus reducing the number of cycles used which would happen if all the arithmetic and other logic ran when it wasn't needed.

Figure 7-1 Voltmeter State Diagram for Machine M0

Figure 7-2 Voltmeter State Diagram for Machines M1 and M2

● 127

Programming the Finite State Machine with 8-Bit PICs in Assembly and C ;PROG_07_01.asm LIST P=16F1823 #INCLUDE #INCLUDE RADIX DEC BOOK_CONFIGURATION CBLOCK 0x70 TICKS STATE_M0 STATE_M1 STATE_M2 Y0 Y1 Y2 Y3 Xa0 Xa1 Xa2 VALUE FLAGS LAPS ENDC CBLOCK 0x20 RESBUFFH RESBUFFL Xb0 Xb1 Xb2 Xc0 Xc1 Xc2 BUFFH BUFFM BUFFL F10V F1V F100mV F10mV FIG ENDC

; Default numbers are to base 10. ; See macro in fsm_macros.inc. ; Used to count 8.2ms ticks.

; Output variable for arithmetic. ; Output variable for arithmetic. ; Output variable for arithmetic. ; ; ; ; ; ; ;

Input variable for arithmetic. Input variable for arithmetic. Input variable for arithmetic. Is the value to be written to the display (10 is blank). The FLAGS bits are: 0 is display update, 1 is division complete, 2 is voltage available, and 3 is reading required. Number of laps with a zero display.

; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;

Declare a block of variables - Bank 0 only. The ADRRESH:ADRESL scaled to millivolts. High byte. The ADRRESH:ADRESL scaled to millivolts. Low byte. Input variable for arithmetic. Input variable for arithmetic. Input variable for arithmetic. General purpose variable for arithmetic. General purpose variable for arithmetic. General purpose variable for arithmetic. Buffer used for arithmetic. Buffer used for arithmetic. Buffer used for arithmetic. Variable holding tens of volts. Variable holding units of volts. Variable holding hundreds of millivolts. Variable holding tens of millivolts. To hold the current significant figure during extraction.

ORG 0X00 GOTO START ORG 0X04 BANKSEL IOCAF BTFSC IOCAF, 5 GOTO $+4 BCF INTCON, TMR0IF INCF TICKS, F RETFIE BCF IOCAF, 5 RETFIE START: MOVLB 1 SET_TMR0_CASE1 BSF INTCON, IOCIE MOVLW 0x24 MOVWF TRISA CLRF TRISC MOVLW 0x09 MOVWF ADCON0 MOVLW 0xE3 MOVWF ADCON1 SET_FREQ_32MHZ MOVLB 2 MOVLW 0x83 MOVWF FVRCON MOVLB 3 CLRF TICKS CLRF STATE_M0 CLRF STATE_M1 CLRF STATE_M2

● 128

; Interrupt vector. ; ; ; ; ; ; ;

Test the interrupt on change flag for RA5. At this line because IOCAF is set. Now jump to clear IOCAF, 5. Clear the tmr0 overflow interrupt flag. Increment by one the 8.2ms ticks. Return from interrupt. Jumped to here to clear IOCAF, 5. Return from interrupt.

; ; ; ; ; ;

Gives 8.2 ms ticks. See fsm_macros.inc. Interrupt on change enable. 0x24 = b'00100100'. Set PORTA RA2 and RA5 as an inputs others as outputs. Set all PORTA as outputs . Select channel AN2 and enable ADC.

; See page 137, right justified result, conversion clock Fosc/64, ; Vref is from the internal referance 4.096 V. ; See file fsm_macros.inc. ; Configure the fixed voltage reference, enable and set to 4.096 V. ; See Chapter 5 Table 5-3, and page 128 in the data sheet. ; ; ; ;

Initialize Initialize Initialize Initialize

count state state state

to to to to

zero. zero. zero. zero.

Chapter 7 ● Digital Voltmeter Project CLRF FLAGS CLRF LAPS MOVLB 0 CLRF F10V CLRF F1V CLRF F100mV CLRF F10mV SWITCH_M0 MOVLB 0 MOVF STATE_M0, W BRW GOTO M0_S0 GOTO M0_S1 GOTO M0_S2 GOTO M0_S3 GOTO M0_S4 GOTO M0_S5 GOTO M0_S6 GOTO M0_S7 GOTO M0_S8 GOTO M0_S9 SWITCH_M1 MOVLB 2 MOVF STATE_M1, W BRW GOTO M1_S0 GOTO M1_S1 SWITCH_M2 MOVF STATE_M2, W BRW GOTO M2_S0 GOTO M2_S1

; Initialize FLAGS to false.

; ; ; ;

Initiate Initiate Initiate Initiate

to to to to

0. 0. 0. 0.

; Move STATE_M0 to the working register. ; Program jumps by the value moved into the working register.

; Bank 2 required for the 7 segment display driver. ; Move STATE_1 to the working register. ; Program jumps by the value moved into the working register.

; Move STATE_2 to the working register. ; Program jumps by the value moved into the working register.

M0_S0 MOVLB 0 MOVLW 61 IF_REG_LESS_THAN_W TICKS GOTO SWITCH_M2 MOVLW 10 MOVWF VALUE BSF FLAGS, 0 BSF FLAGS, 3 CLRF TICKS INCF STATE_M0, F GOTO SWITCH_M2 M0_S1 MOVLB 0 MOVLW 122 IF_REG_LESS_THAN_W TICKS GOTO SWITCH_M2 BTFSS FLAGS, 2 GOTO $+5 MOVLW 3 CLRF LAPS MOVWF STATE_M0 GOTO SWITCH_M2 ; This block when probe voltage MOVLW 2 MOVWF STATE_M0 GOTO SWITCH_M2

; Waiting for 10 mV display to end ; Setting up for a blank. ; ; ; ;

Update display bit. Trigger a read in machine 2. Reset TICKS for next state. Increment the state number.

; O.5 s delay after complete reading. ; ; ; ;

Test for a non zero reading available. Here because reading is below 111 mV. Here because reading is above 111 mV. Clear the laps counter as there is voltage to read.

is below 111 mV. ; No non zero reading available go to state 2.

M0_S2 MOVLW 3 IF_REG_GREATER_THAN_OR_EQUAL_TO_W LAPS GOTO $+11 ; Here if LAPS == 3. INCF LAPS, F ; Here if LAPS < 3. CLRF F10V CLRF F1V CLRF F100mV CLRF F10mV CLRF VALUE BSF FLAGS, 0 MOVLW 3 MOVWF STATE_M0

● 129

Programming the Finite State Machine with 8-Bit PICs in Assembly and C GOTO SWITCH_M2 ; This block when LAPS == 3. CLRF LAPS BANKSEL FVRCON BCF FVRCON, FVREN ; Disable fixed voltage reference. BANKSEL IOCAP BSF IOCAP, 5 ; Interrupt on change enabled on RA5 for positive going edges. SLEEP BCF IOCAP, 5 BANKSEL FVRCON BCF FVRCON, FVREN CLRF STATE_M0 GOTO SWITCH_M2

; Interrupt on change disabled on RA5 for positive going edges ; Enable fixed voltage reference.

M0_S3 MOVLB 0 MOVF F10V, W MOVWF VALUE BSF FLAGS, 0 CLRF TICKS INCF STATE_M0, F GOTO SWITCH_M2 M0_S4 MOVLB 0 MOVLW 61 IF_REG_LESS_THAN_W TICKS GOTO SWITCH_M2 MOVLW 10 MOVWF VALUE BSF FLAGS, 0 CLRF TICKS INCF STATE_M0, F GOTO SWITCH_M2 M0_S5 MOVLB 0 MOVLW 31 IF_REG_LESS_THAN_W TICKS GOTO SWITCH_M2 MOVF F1V, W MOVWF VALUE BSF FLAGS, 0 CLRF TICKS INCF STATE_M0, F GOTO SWITCH_M2 M0_S6 MOVLB 0 MOVLW 61 IF_REG_LESS_THAN_W TICKS GOTO SWITCH_M2 MOVLW 10 MOVWF VALUE BSF FLAGS, 0 CLRF TICKS INCF STATE_M0, F GOTO SWITCH_M2 M0_S7 MOVLB 0 MOVLW 31 IF_REG_LESS_THAN_W TICKS GOTO SWITCH_M2 MOVF F100mV, W MOVWF VALUE BSF FLAGS, 0 CLRF TICKS INCF STATE_M0, F GOTO SWITCH_M2 M0_S8 MOVLB 0 MOVLW 61 IF_REG_LESS_THAN_W TICKS GOTO SWITCH_M2 MOVLW 10

● 130

; Setting up for F10V

; Waiting for F10V display

to end.

; Setting up for a blank.

; Waiting for blank display to end. ; Setting up for F1V

; Waiting for F1V display

to end.

; Setting up for a blank.

; Waiting for blank display to end. ; Setting up for F100mV

; Waiting for F100mV

display to end.

; Setting up for a blank.

Chapter 7 ● Digital Voltmeter Project MOVWF VALUE BSF FLAGS, 0 CLRF TICKS INCF STATE_M0, F GOTO SWITCH_M2 M0_S9 MOVLB 0 MOVLW 31 IF_REG_LESS_THAN_W TICKS GOTO SWITCH_M2 MOVF F10mV, W MOVWF VALUE BSF FLAGS, 0 CLRF TICKS CLRF STATE_M0 GOTO SWITCH_M2 SEVEN_SEG_DRIVE_MACHINE M2_S0 BTFSS FLAGS, 3 GOTO SWITCH_M1 INCF STATE_M2, F GOTO SWITCH_M1

; Waiting for blank to end. ; Setting up for F10mV

FLAGS, VALUE, M1_S0, M1_S1

; Machine M1

; Here if a measurement is not called for. ; Here if

a measurement is called for.

M2_S1 BCF FLAGS, 3 ; Reset the reading required gets set by MO when in M0_S12. MOVLB 1 ; Change to bank 1 for ADCON0, and ADRESH:ADRESL. BSF ADCON0, 1 ; Start a to d conversion BTFSC ADCON0, 1 ; This and the next line loop round until the conversion is finished. GOTO $-1 BCF FLAGS, 2 ; Set the reading available to false. MOVF ADRESH, F BTFSS STATUS, Z GOTO M2_S1_L0 ; Here because ADDRESH is not zero MOVLW 5 IF_REG_GREATER_THAN_OR_EQUAL_TO_W ADRESL ; Test to see if probe voltage is ; greater than 111 mV. GOTO M2_S1_L0 ; Voltage greater then 111 mV. CLRF STATE_M2 ; Voltage not greater then 111 mV. GOTO SWITCH_M1 M2_S1_L0 ; Label 0 in this state BSF FLAGS, 2 ; Jumped to here because voltage greater then 111 mV. ; Set the reading available to true. MOVLB 1 MOVF ADRESH, W MOVWF Xa1 ; Move ADRESH into Xa1. MOVF ADRESL, W MOVWF Xa0 ; Move ADRESL into Xa0. MOVLB 0 MOVLW HIGH 11091 ; Scaling factor numerator hight byte, see text in the book. MOVWF Xb1 MOVLW LOW 11091 ; Scaling factor numerator, low byte, see text in the book. MOVWF Xb0 ;q Scaling is done by first multiplying by 1091, then dividing 500. See text in the book. CALL MULT_TWO_SIXTEEN_BIT_NUMBERS MOVF Y0, W MOVWF Xa0 ; The result of the multiplication is tranferred from Y to Xa. MOVF Y1, W MOVWF Xa1 MOVF Y2, W MOVWF Xa2 CLRF Xb2 ; Xb2 and Xb1 are cleared then 500 moved into Xb0 for the division. MOVLW HIGH 500 MOVWF Xb1 MOVLW LOW 500 MOVWF Xb0 CAll DIV_24_BY_16_BIT ; Scaling is completed by dividing by 500. MOVLB 0 MOVF Y1, W MOVWF RESBUFFH ; The scaled voltage reading is now moved to RESBUFF. ; The scaling gives the voltage in 10s of mV. MOVF Y0, W MOVWF RESBUFFL MOVLB 0 MOVLW HIGH 10000 MOVWF Xb1

● 131

Programming the Finite State Machine with 8-Bit PICs in Assembly and C MOVLW LOW 10000 MOVWF Xb0 CALL EXTRACT_SIG_FIGS

; The extract significant figures subroutine uses 10000 as the ; denominator to extract 10s of volts.

MOVF FIG, W MOVWF F10V ; Load F10V with tens of volts. MOVLW HIGH 1000 MOVWF Xb1 MOVLW LOW 1000 MOVWF Xb0 CALL EXTRACT_SIG_FIGS ; The extract significant figures subroutine uses 1000 as the ; denominator to extract units of volts. MOVF FIG, W MOVWF F1V CLRF Xb1 MOVLW 100 MOVWF Xb0 CALL EXTRACT_SIG_FIGS

; Load F1V with units of volts.

; The extract significant figures subroutine uses 100 as the ; denominator to extract 100s of millivolts.

MOVF FIG, W MOVWF F100mV CLRF Xb1 MOVLW 10 MOVWF Xb0 CALL EXTRACT_SIG_FIGS

; Load F100mV with hundreds of millivolts.

; The extract significant figures subroutine uses 10 as the ; denominator to extract 10s of millivolts.

MOVF FIG, W MOVWF F10mV CLRF STATE_M2 GOTO SWITCH_M1

; Load F10mV with tens of millivolts.

; See the state diagram in chapter 6 Fig 6-7 DIV_24_BY_16_BIT ; Y = Xa/Xb, DIV_S0 CLRF Y0 ; Set Y to zero. CLRF Y1 CLRF Xc1 ; Set Xc to zero. CLRF Xc0 MOVF Xb1, F BTFSS STATUS, Z GOTO $+4 ; Here because Xb1 is not 0. MOVF Xb0, F ; Here because Xb1 is 0. BTFSC STATUS, Z RETURN ; Here because Xb0 is 0. Also Xb1 is 0. MOVF Xa2, W MOVWF BUFFH ; Load Xa high byte to BUFFH. MOVF Xa1, W MOVWF BUFFM ; Load Xa high byte to BUFFM. MOVF Xa0, W MOVWF BUFFL ; Load Xa high byte to BUFFM. BCF FLAGS, 1 ; Ensure end of division flag low. DIV_S1 BTFSC FLAGS, 1 RETURN MOVF BUFFH, W MOVWF Xc2 MOVF BUFFM, W MOVWF Xc1 MOVF BUFFL, W MOVWF Xc0 MOVF Xb0, W SUBWF BUFFL, F MOVF Xb1, W SUBWFB BUFFM, F CLRW SUBWFB BUFFH, F BTFSS STATUS, C BSF FLAGS, 1 DIV_S2 BTFSC FLAGS, 1 GOTO DIV_S1 INCF Y0, F

● 132

; Here because end of division flag set. ; Xc will contain the remainder. ; Xc will contain the remainder. ; Xc will contain the remainder. ; Subrtact Xb0 from BUFFL result in BUFFL. ; Subrtact Xb1 from BUFFM result in BUFFM. ; Subtract the carry if there is one. ; Here because C == 0 therefore below zero.

; ; ; ;

Because BUFF is now less then Xb this is the last loop and Xc contains the remainder. Counting the number of times Xb has been subtracted from BUFF with a positive result.

Chapter 7 ● Digital Voltmeter Project BTFSC STATUS, Z INCF Y1, F GOTO DIV_S1

; If Y0 rolls over to 0, Y1 has to be incremented with the carry.

MULT_TWO_SIXTEEN_BIT_NUMBERS MULT_S0 CLRF Y3 ; CLRF Y2 ; CLRF Y1 ; CLRF Y0 ; MOVF Xb1, F ; BTFSS STATUS, Z ; GOTO $+4 ; MOVF Xb0, F ; BTFSC STATUS, Z ; RETURN ; MOVF Xa1, W MOVWF BUFFH ; MOVF Xa0, W MOVWF BUFFL ; MULT_S1 MOVF BUFFH, F ; BTFSS STATUS, Z ; GOTO $+4 ; MOVF BUFFL, F BTFSC STATUS, Z ; RETURN ; ADD_REGISTERS_16 Y1, Y0, MOVLW 1 SUBWF BUFFL, F CLRW SUBWFB BUFFH, F GOTO MULT_S1 EXTRACT_SIG_FIGS ; MOVLB 0 CLRF Xa2 MOVF RESBUFFH, W MOVWF Xa1 MOVF RESBUFFL, W MOVWF Xa0 CALL DIV_24_BY_16_BIT MOVF Y0, W MOVWF FIG ; MOVF Xc1, W ; MOVWF RESBUFFH MOVF Xc0, W ; MOVWF RESBUFFL RETURN

; Y = Xa*Xb, note each variable has a high and low byte. Clear output to zero. Here because Xb0 is not zero. Clear output to zero. Clear output to zero. Clear output to zero. By moving a file to itself the Z flag in STATUS gets affected. Test Xb1. Here because Xb1 is not zero. Here because Xb1 is zero. Test Xa0. Here because Xb1 and Xa0 are both zero. Move Xa1 to BUFFH. Move Xa0 to BUFFL.

By moving a file to itself the Z flag in STATUS gets affected. Test BUFFH. Here because BUFFH is not zero. ; Here because BUFFH is zero. Test BUFFL. Here because BUFFH and BUFFL are both zero. Xb1, Xb0, Y3, Y2, Y1, Y0

Subroutine is called with the divisor in Xb1:Xb0.

The extracted significant figure. FIG = RESBUFF/Xb. Remainder from the division RESBUFF/Xb. Remainder from the division RESBUFF/Xb.

END

Program 7-1 Voltmeter Program

7.3 • Scaling the Raw Analog Value

Machine M3 deals with the analog-to-digital conversion. It puts the raw value in ADRESH:ADRESL with the ten-bit result right justified so that the eight least significant bits are in ADRESL and the two most significant bits are in ADRESH. This is configured in ADCON1.

● 133

Programming the Finite State Machine with 8-Bit PICs in Assembly and C ADCON1

E

3

ADRESH:ADRESL Right justified. Eight least significant bits in ADRESL

7

1

6

1

5

1

4

0

3

0

Unimplemented

2

0

Unimplemented

1

1

0

1

V REF + is connected to internal Fixed Voltage Reference (FVR) module

Set the ADC clock period to 2 μS for 32 Mhz clock speed. See Table 16-1 in the data sheet

Table 7-1 ADCON1

The reference voltage is 4.096 V or 4096 mV. This equates to an A-D conversion of 1024. A change of one least significant bit is therefore 4096/1024 or 4 mV. From Fig 5-2, the input voltage is reduced by the input potential divider by a factor of 22/122. A change of one least significant bit means a change of 4x122/22 mV, equal to 22.1818 mV at the voltmeter probes. To read in millivolts, we have to multiply the result in ADRESH:ADRESL by 22.1818. However, we can not do floating-point arithmetic only integer arithmetic. Therefore we need to find two integers with a ratio of 22.1818:1. Now, 22.1818/1 is the same as 221818/10000. We need to multiply by 221818 then divide by 10000. We do, however, need to tread carefully here when multiplying 221818 x 1023, which is the highest possible result. It will give 226919814 (28 bits). This is too big for our 24-bit dividend. By dividing by the numerator and denominator first by 20 to get 11090.9/500 rounded to 11091/500, it gives a ratio of 22.182. The maximum product will be 1023 x 11091, equal to 11,346,093 (24 bits). There is one more thing to watch out for when scaling with integers. This is to multiply before you divide, otherwise, there will be a loss in precision. For example, if I want to multiply 60 by 1.125, this could be done by multiplying by 9 and then dividing by 8. 60 x 1.125 = 67.5 correct answer. Using integer arithmetic Multipying first: 60 x 9 = 540 540/8 = 67 integer result we lose the 0.5. Dividing first: 60/8 = 7 integer result we lose the 0.5. 7 * 9 = 63 bigger error.

● 134

Chapter 7 ● Digital Voltmeter Project

The details of the arithmetic are commented on in the program. The voltage measured at the probes and now scaled to millivolts is moved to RESBUFF. The result buffer is made up of the two files: RESBUFFH:RESBUFFL. The individual significant figures for tens of volts, volts, hundreds of millivolts, and tens of millivolts are now extracted. 7.4 • Extracting the individual figures for the display

Each figure is obtained by calling subroutine EXTRACT_SIG_FIGS. The routine is called four times: once for each figure with a different divisor for each figure. Suppose the voltage read is 12345 mV or 12.345 V. We want our display to first present the 1, then the 2, 3 and lastly the 4. 5 will be discarded as we are only resolving to tens of millivolts. The value 12345 is held in RESBUFF and each time the EXTRACT_SIG_FIGS routine is called, it divides the RESBUFF by a divisor placed in Xb. The integer division result is placed in the FIG file and the remainder after the division is placed in RESBUFF to used on the next call. So starting by dividing by 10000, RESBUFF(12345)/10000 gives a result of 1 and a remainder of 2345 to go into RESBUFF. 1 is the number of tens of volts and is placed in file F10V. On the next call RESBUFF(2345)/1000 gives a result of 2 and a remainder of 345 to go into RESBUFF. 2 is the number of volts and is placed in file F1V RESBUFF(345)/100 gives a result of 3 and a remainder of 45 to go into RESBUFF. 3 is the number of hundreds of millivolts and is placed in F100mv. Finally, on the last call, RESBUFF(45)/10 gives a result of 4 and a remainder of 5 that we don’t need. 4 is the number of tens of millivolts and is placed in the F10mv file. You can follow this through in state 2 and the subroutine. 7.5 • Detecting No Input Voltage

The detection of no input voltage or somewhat less than 111 mV is done by comparing the value in ADRESH:ADRESL with 5. The two registers hold the 10-bit result, right justified so the 8 least most significant bits are held in ADRESL and the 2 most significant bits in ADRESH. First ADRESH is checked to see if it is zero. ADRSEL is then checked to see if it is less than 5. A value of 5 is 20 mV at the input pin on the PIC, or 111 mV at the probe. 7.6 • Recalibration

You can easily change the voltage range to suit your needs by using a different potential divider and recalculating the scaling. Be mindful of the voltages you are playing with. If you are sure of your electrical knowledge, you could even make it into an ammeter by connecting the analog input across a shunt resistor.

● 135

Programming the Finite State Machine with 8-Bit PICs in Assembly and C

Chapter 8 • Troubleshooting and Planning 8.1 • Introduction

If you have been experimenting with the programs or have been trying to write your own from scratch, you will likely have experienced the odd bug or even a completely inoperative program. Assembly language programs can be unfriendly. The assembler will warn you of some things such as syntax errors or jumps to labels that aren't recognised. It won't however, help you with the functionality of your code. This chapter will try to help you debug your code when it doesn’t work, or better, stop you from making the mistakes. Do not be put off if you make mistakes when coding. I very rarely get code to run the first time. One way you can produce reliable code is to keep testing it. This chapter covers both fixing bugs and the planning of programs to avoid bugs. Most of the advice in this chapter applies to general programming. If you are writing in C, Python, Java or programming an industrial programmable logic controller (PLC), the same things are true. 8.2 • Have an Overview of the Project

You need to have a clear idea of what the project will do. Just letting it grow can lead to you having to start again. If you take a look at Figures 7-1 and 7-2 (the state charts for the voltmeter), you have a representation of what the entire program does. Three separate state machines run concurrently with each state being responsible for a limited range of activity. You can see what the program does without getting bogged down in local detail. 8.2.1 • State Diagrams and Flow Charts

You will have seen flow charts: they crop up in all sorts of places, wall charts, instruction booklets, even cartoons. They are generally easy to understand as they can be seen as routes through actions and decisions. Looking at state charts, you might think they are just the same, but they are different. As an example, imagine a central heating system for a building where there is a boiler and a circulation pump. To control the system, there is a master switch and a thermostat. When the master switch is on, the system is enabled and when it is off it's disabled. If the system is enabled, the boiler and pump will run if the temperature is below the setpoint configured on the thermostat. They will stop when the temperature reaches the setpoint. Figure 8-1 compares a state diagram with a flow chart to describe the system. The FSM, in this case, is shown as a Moore machine where the outputs are dependent only on the state. On the diagram, the outputs are shown inside the states. If you trace your way around both diagrams, you will see the same functionality but the flow diagram has no concept of state. If the machine is in state 2 where the master switch is on and the boiler and pump are off, it will remain in that state until either the master switch is turned off, causing a transition to state 0, or the temperature drops below the setpoint with the master switch set to on, in which case the transition will be to state 1 to fire the boiler and start the pump. The machine will then stay in this state until another transition is called for. In the flow chart, the equivalent of state 2 is the continuous loop shown by the dotted ellipse. You can pick out the other loops that equate to the two other states. When it comes to converting these charts into code and then debugging, I find that

● 136

Chapter 8 ● Troubleshooting and Planning

the statechart wins. With a state machine, it is easier to separate code that has a particular purpose. This leads to the subject of the next paragraph.

Figure 8-1 State diagram and Flow Chart

8.3 • Break Big Problems Down Into Smaller Ones

If your project does several things, break it down into different parts. In the voltmeter project, the program has to read input voltage using the analog-to-digital converter, then decide on whether the voltage is above 111 mV or not. If it is, the raw result of this conversion has to be scaled to represent millivolts. The individual figures that make up the output have to be extracted until all the functionality of the project is achieved. Each of these operations should be seen as a separate problem. This is where the state machine approach can help because you can have states that do one job or machines that do a small number of jobs. Subroutines and macros also help towards reliability. Having produced some code that works, it can be used again and depended on. When you come to developing code, write it in small parts so that they can be tested on their own. This testing can be done by writing short mini-projects to test individual blocks of code. 8.4 • Read Through Your Code in Detail and Add Comments

Take your time reading through your code. Carefully try to visualize what is happening. To start with, add comments to every line. This will force you to think about every detail. You might be the only person who ever reads these comments but they will be useful when you look at the code at a later date. If you make changes for any reason, keep your state chart synchronized with the code and update your comments. Incorrect comments are can be very confusing! 8.5 • Debugging a Running Program

As this book does not call for the use of Microchip’s PICKIT programmer/debugger, we are instead using the FTDI lead and the programs provided for the book. We can’t make use of

● 137

Programming the Finite State Machine with 8-Bit PICs in Assembly and C

“in-circuit” debugging. We can, however, make use of the FTDI lead and the serial_reader program and output data to a computer. This is the equivalent of putting print statements in Python, C, or another language to assist with debugging. What we will be doing is writing a debug macro that can be inserted anywhere to output a variable value to the computer to see what is happening or not. 8.6 • Traffic Lights

This is a small project to practice the points outlined above. The first step is to define what the program will do. Our traffic lights will consist of six LEDs that represent the two sets of red, yellow, and green lights. They imitate the type used on road works to control the flow of single file traffic.

Figure 8-2 Road Works Traffic Control

On power-up, both sets of lights will be on red and remain so for 10s. Then set A will change to red and yellow for 2s, then to green. After a further 20 seconds, Set A will change to yellow for 5s and then to red. Both sets will remain on red for 10s before set B goes through the same changes. This cycle will repeat until power is removed. Also, there is a signal that enables the light sequence. If this signal goes low, the sequence stops at the next double red. The enable signal must be high for the entire 10s period when both sets are on red before starting the sequence. From the written description, we need to identify the different states that the system can be in.

● 138

Chapter 8 ● Troubleshooting and Planning State

Lights A

Lights B

0

Red

Red

1

Red - Yellow

Red

2

Green

Red

3

Yellow

Red

4

Red

Red

5

Red

Red-Yellow

6

Red

Green

Red

Yellow

7

Table 8-1 Traffic Light States

Having identified the eight states that are required, the next step is to work out the allowable transitions and input conditions to trigger these conditions. This is a very simple example, in that the states follow around one after another with only a single allowable transition to the next state. So far, throughout the book, state machines have followed the Mealy format where outputs are dependent on the current state. Inputs and outputs are shown on the diagram next to the transition arrows. The Moore machine is different in that the outputs only depend on the current state. For this project, I will use the Moore machine form. The outputs only depend on the current state and are shown in the diagram inside the box for each state.

Figure 8-3 Road Works State Chart

The transition to the next state is determined by the elapsed time in a particular state except for the leaving states 0 and 4 where there are two reds. In these states, the enable signal must be present for 10s. This will cover our requirement for the sequence to continue to the next two reds state if the enable signal goes low. We now have a clear idea of what

● 139

Programming the Finite State Machine with 8-Bit PICs in Assembly and C

the program will do. We can run through its operation without thinking about the code. 8.6.1 • A Circuit Diagram

Before you write the code you need to plan the circuit so you know the inputs and output that are going to be used. The FTDI lead connections are the same and there are six LEDs connected through current limiting resistors. The value of these resistors is not critical: 1kΩ will give about 3mA.

Figure 8-4 Circuit Diagram

8.6.2 • Separate Different Problems

This program needs a timer to determine when to change state. We have a tested method to implement this from previous programs that involve counting ticks generated by an interrupt from timer0. For this particular program, we have to reset a tick counter for each state. In previous programs that were Mealy machines, the counter reset was an output during state transition. As we are using a Moore machine, it creates a problem in that we can't reset the counter with an output from the current state as it will remain held at reset forever. One solution is to add a state between existing states just to reset the counter. Another is to have a different counter for the next state and reset it in the current state. Many developments have occurred since the Moore and Mealy machines were invented. You might like to look up the topic of Harel statecharts to see how things have evolved. Some variations can be used to make things more flexible. One of these is the idea of sub-states where a state machine is nested inside a state.

● 140

Chapter 8 ● Troubleshooting and Planning

Figure 8-5 Use of Sub-States

Figure 8-5 shows how two sub-states are added to the main S7 and S0 states. When substates are used, the main state can still set outputs and respond to inputs, but we have the additional functionality of being able to run a sub-state machine within it. Transitions out of the main state can be made to occur, depending on either conditions to do with the main state or one of the sub-states. In this example, exit from sub-state SS_1 causes the transition. The main state sets the outputs for the LEDs and sub-state SS_0 is entered to reset the tick counter. Once the tick counter has been reset, the machine stays in S7, SS_1 waiting for the time-period to elapse. The only other thing we need is the enable signal from a switch. No debounce precautions are needed and the input that the switch is connected to only has to be looked at in states S0 and S4. The complete state diagram will have sub-states in each main state. S0 and S4 will be the same as will S1, S2, S3, S5, S6, and S7. 8.6.3 • Producing the Code

Before producing the code that runs the above state machine, the PIC needs a particular configuration, including configuration file settings, oscillator frequency, and port configuration. It is best to progress in small parts, testing as you go. This testing will take the form of adding debug code to prove that each bit you add works and can be depended on. Writing a complete program and then trying to get it to work is not the way to go. The program needs to count in seconds up to 20. We will start by writing enough code to achieve this and then test it. Drawing from previous programs, we can create an interrupt based counter, based on timer0 generating 8.2ms ticks. 122 ticks are approximately 1s. Program 8-1 is the test program for the timer.

● 141

Programming the Finite State Machine with 8-Bit PICs in Assembly and C ;PROG_08_01.asm LIST P=16F1823 #INCLUDE #INCLUDE RADIX DEC BOOK_CONFIGURATION

; Default numbers are to base 10. ; See macro in fsm_macros.inc.

CBLOCK 0x70 TICKS SECONDS ENDC

; Used to count 8.2ms ticks. ; Used to hold the seconds count.

ORG 0X00 GOTO START ORG 0X04 BCF INTCON, TMR0IF DECF TICKS, F BTFSS STATUS, Z GOTO $+4 INCF SECONDS, F MOVLW 122 MOVWF TICKS RETFIE START MOVLB 1 SET_FREQ_32MHZ SET_TMR0_CASE1 MOVLB 3 SET_SERIAL_TX_16F1823 MOVLB 3

; ; ; ; ; ;

Interrupt vector. Clear the tmr0 overflow interrupt flag. Decrement by one the 8.2ms ticks. Test to see if TICKS has reached zero. If TICKS has not reached zero jump to return from interrupt. If TICKS has reached zero increment the seconds count.

; Restore TICKS to 122 (122 x 8.2 ms 1 second). ; Return from interrupt.

; ; ; ; ; ; ;

Bank 1 required for the following macros. See file fsm_macros.inc. Gives 8.2 ms ticks. See fsm_macros.inc. Bank 3 required for the following macro. Configure the serial comms, see file fsm_macros.inc. Leaves the previous macro in bank 2, bank 3 required for the following macro.

S0 BTFSS SECONDS, 0 GOTO S0

; Will skip the bext line if least significant bit is high. ; Stays in state 0 while least significant bit is low.

DEBUG SECONDS

; Transmits odd seconds

BTFSC SECONDS, 0 GOTO S2

; Will skip the next line if least significant bit is low. ; Stays in state 0 while least significant bit is high.

DEBUG SECONDS GOTO S0

; Transmits even seconds

S1

S2

S3

END

Program 8-1 Trafic Lights Developing the Timer

Beginning at the START label there are three macros: • to set the frequency, • to set up timer0 to produce the 8.2 ms interrupt ticks • and lastly, one to set up the serial comms that will work with the serial read program. Going back a few lines in the code at the interrupt vector is the interrupt service routine. There is only one interrupt in play: from timer0. The first thing is to reset the timer0 interrupt flag TMROIF. Next, the TICKS variable is decremented by one and the zero flag in the STATUS register is tested to see if TICKS has reached zero. If it has, the program skips one line and the SECONDS variable is incremented to count another second. TICKS is then reloaded with the value 122 followed by the return from interrupt. To test the program so far, we will transmit the value of the SECONDS file over a serial

● 142

Chapter 8 ● Troubleshooting and Planning

lead to a computer. As we will also be doing other tests, it makes sense to have a macro that can be placed in programs wherever we want to monitor a value. Here is the macro. There is one argument: that is the register or file to be monitored. Bank 3 must be selected before using it. DEBUG MACRO REG_TO_TX MOVF REG_TO_TX, W MOVWF TXREG BTFSS TXSTA, TRMT GOTO $-1 ENDM

; Bank 3 required. EUSART has to be configured to transmit. ; ; ; ;

Start transmission by loading TXREG Poll TXSTA bit TRMT to see if the transmit shift register is empty. See datasheet page 271, paragraph 26.1.1.14. Leaves in bank 3.

Debug Macro

To read the SECONDS file, we could just put the DEBUG macro in a loop and it would constantly transmit the value held in SECONDS. Unfortunately, this can result in incorrect values being read. The problem can be solved by introducing a 25 ms delay between transmissions but you get about 30 outputs per second... 29 more than you need. A more elegant solution is to transmit the SECONDS value after each rising and falling edge of the least significant bit (LSB) of the SECONDS file. A simple four-state machine provides a way of detecting edges. This is shown in Fig 8-6. Starting in state 0, if the least LSB of SECONDS is high there will be a transition to S1 where the value of SECONDS will be transmitted. After transmission, there will be a transition to S2 where the machine will stay waiting for the LSB to go low and then transition to S3 to transmit again before returning to S0 to await the LSB going high again. S1 transmits odd values of SECONDS. S3 transmits even values. This is implemented as a Moore machine where the outputs are only dependent on the current state. A Mealy machine would only need two states because transmissions would be done on state transitions. The effect on the code, in this case, is practically none as states 1 and 3 are merely labels.

Figure 8-6 State Machine to Detect Edges

● 143

Programming the Finite State Machine with 8-Bit PICs in Assembly and C

Figure 8-7 Testing the Second Count

Now that the second count is shown to be correctly working, more of the program can now be written. As there are six outputs to be driven. We can use the code we have to prove the outputs and LEDs work correctly by changing the temporary state machine to add two more states so the LEDs are switched on and off in turn on each edge of the SECONDS LSB. Also, RA5 is configured as an input for the enable signal. In the state diagram below, the machine stays in state 0 unless the LSB goes high and the enable signal is high.

Figure 8-8 Testing Outputs

● 144

Chapter 8 ● Troubleshooting and Planning ;PROG_8_02.asm LIST P=16F1823 #INCLUDE #INCLUDE RADIX DEC BOOK_CONFIGURATION

; Default numbers are to base 10. ; See macro in fsm_macros.inc.

CBLOCK 0x70 TICKS SECONDS ENDC

; Used to count 8.2ms ticks. ; Used to hold the seconds count.

ORG 0X00 GOTO START ORG 0X04 BCF INTCON, TMR0IF DECF TICKS, F BTFSS STATUS, Z GOTO $+4 INCF SECONDS, F MOVLW 122 MOVWF TICKS RETFIE START MOVLB 1 SET_FREQ_32MHZ SET_TMR0_CASE1 CLRF TRISC MOVLB 3 SET_SERIAL_TX_16F1823 MOVLB 0

; ; ; ; ; ;

Interrupt vector. Clear the tmr0 overflow interrupt flag. Decrement by one the 8.2ms ticks. Test to see if TICKS has reached zero. If TICKS has not reached zero jump to return from interrupt. If TICKS has reached zero increments the seconds count.

; Restore TICKS to 122 (122 x 8.2 ms 1 second). ; Return from interrupt.

; ; ; ; ; ;

Bank 1 required for the following macros, and TRISC. See file fsm_macros.inc. Gives 8.2 ms ticks. See fsm_macros.inc. Set all PORTC as outputs. Bank 3 required for the following macro. Configure the serial coms, see file fsm_macros.inc.

S0 MOVLW b'00100000' MOVWF PORTC BTFSS SECONDS, 0

; Turn Red A (RC5) on others off. ; Stay in this state until LSB in SECONDS goes high. Then skip to ; check enable signal on RA5.

GOTO S0 BTFSS PORTA, 5 GOTO S0

; Here because LSB high, still stay in this state unless RA5 high.

MOVLW b'00010000' MOVWF PORTC BTFSC SECONDS, 0 GOTO S1

; Turn Yellow A (RC4) on others off. ; Stay in this state until LSB in SECONDS goes low.

MOVLW b'00001000' MOVWF PORTC BTFSS SECONDS, 0 GOTO S2

; Turn Green A (RC3) on others off. ; Stay in this state until LSB in SECONDS goes high.

MOVLW b'00000100' MOVWF PORTC BTFSC SECONDS, 0 GOTO S3

; Turn Red B (RC2) on others off. ; Stay in this state until LSB in SECONDS goes low.

MOVLW b'00000010' MOVWF PORTC BTFSS SECONDS, 0 GOTO S4

; Turn Yellow B (RC1) on others off. ; Stay in this state until LSB in SECONDS goes high.

S1

S2

S3

S4

S5 MOVLW b'00000001' MOVWF PORTC BTFSC SECONDS, 0 GOTO S5 GOTO S0

; Turn Green B (RC0) on others off. ; Stay in this state until LSB in SECONDS goes low.

END

Program 8-2 Test Inputs and Outputs

● 145

Programming the Finite State Machine with 8-Bit PICs in Assembly and C

Now that the timekeeping and inputs and outputs have been proved to work, the program can be written in its final form. Fig 8-3 provided a top-level view of what we want, but substates were introduced as in Fig 8-5 to deal with the resetting of the SECONDS counter. For clarity, the whole program is reproduced in Fig 8-9, followed by the complete program.

Figure 8-9 Complete Traffic Lights State Chart ;PROG_8_03.asm LIST P=16F1823 #INCLUDE #INCLUDE RADIX DEC BOOK_CONFIGURATION

; Default numbers are to base 10. ; See macro in fsm_macros.inc.

CBLOCK 0x70 TICKS SECONDS ENDC

; Used to count 8.2ms ticks. ; Used to hold the seconds count.

ORG 0X00 GOTO START ORG 0X04 BCF INTCON, TMR0IF DECF TICKS, F BTFSS STATUS, Z GOTO $+4 INCF SECONDS, F MOVLW 122 MOVWF TICKS RETFIE

● 146

; ; ; ; ; ;

Interrupt vector. Clear the tmr0 overflow interrupt flag. Decrement by one the 8.2ms ticks. Test to see if TICKS has reached zero. If TICKS has not reached zero jump to return from interrupt. If TICKS has reached zero increment the seconds count.

; Restore TICKS to 122 (122 x 8.2 ms 1 second). ; Return from interrupt.

Chapter 8 ● Troubleshooting and Planning START MOVLB 1 ; Bank 1 required for the following macros, and TRISC. SET_FREQ_32MHZ ; See file fsm_macros.inc. SET_TMR0_CASE1 ; Gives 8.2 ms ticks. See fsm_macros.inc. CLRF TRISC ; Set all PORTC as outputs. MOVLB 0 ; Select bank 0 for PORTA and PORTC. ;=============================================================================================== S0 MOVLW b'00100100' MOVWF PORTC ; Turn Red A (RC5) and Red B (RC2) on others off. SS0_0 CLRF SECONDS ; Clear the seconds counter. SS0_1 BTFSS PORTA, 5 GOTO SS0_0 ; If the enable signal is low return to sub-state 0. MOVLW 10 IF_REG_LESS_THAN_W SECONDS GOTO SS0_1 ; If less than 10 s have elapsed stay in substate 1. ;=============================================================================================== S1 MOVLW b'00110100' MOVWF PORTC ; Turn Red A (RC5) and Yellow A (RC4) and Red B (RC2) on others off. SS1_0 CLRF SECONDS ; Clear the seconds counter. SS1_1 MOVLW 2 IF_REG_LESS_THAN_W SECONDS GOTO SS1_1 S2 MOVLW b'00001100' MOVWF PORTC ; Turn Green A (RC3) and Red B (RC2) on others off. SS2_0 CLRF SECONDS SS2_1 MOVLW 20 IF_REG_LESS_THAN_W SECONDS GOTO SS2_1 ; If less than 20 s have elapsed stay in substate 1. ;=============================================================================================== S3 MOVLW b'00010100' MOVWF PORTC ; Turn Yellow A (RC4) and Red B (RC2) on others off. SS3_0 CLRF SECONDS ; Clear the seconds counter. SS3_1 MOVLW 5 IF_REG_LESS_THAN_W SECONDS GOTO SS3_1 ; If less than 5 s have elapsed stay in substate 1. ;=============================================================================================== S4 MOVLW b'00100100' MOVWF PORTC ; Turn Red A (RC5) and Red B (RC2) on others off. SS4_0 CLRF SECONDS ; Clear the seconds counter. SS4_1 BTFSS PORTA, 5 GOTO SS4_0 ; If the enable signal is low return to sub-state 0. MOVLW 10 IF_REG_LESS_THAN_W SECONDS GOTO SS4_1 ; If less than 10 s have elapsed stay in substate 1 ;=============================================================================================== S5 MOVLW b'00100110' MOVWF PORTC ; Turn Red A (RC5) and Red B (RC2) and Yellow B (RC1)on others off. SS5_0 CLRF SECONDS ; Clear the seconds counter. SS5_1 MOVLW 2 IF_REG_LESS_THAN_W SECONDS GOTO SS5_1 ; If less than 2 s have elapsed stay in substate 1 . ;=============================================================================================== S6 MOVLW b'00100001' MOVWF PORTC ; Turn Red A (RC5) and and Green B (RC0) on others off. SS6_0 CLRF SECONDS ; Clear the seconds counter. SS6_1 MOVLW 20 IF_REG_LESS_THAN_W SECONDS

● 147

Programming the Finite State Machine with 8-Bit PICs in Assembly and C GOTO SS6_1 ; If less than 20 s have elapsed stay in substate 1. ;=============================================================================================== S7 MOVLW b'00100010' MOVWF PORTC ; Turn Red A (RC5) Yellow B (RC1) on others off. SS7_0 CLRF SECONDS ; Clear the seconds counter. SS7_1 MOVLW 5 IF_REG_LESS_THAN_W SECONDS GOTO SS7_1 ; If less than 5 s have elapsed stay in substate 1. GOTO S0 END

Program 8-3 Complete Traffic Lights Program 8.7 • Using Debug Macro on the Voltmeter Programmable

One of the reasons why finite state programming is so useful is the way the code that is running in any given state is isolated from other code in the program. If you can monitor the state variable’s value, you can see what the program is doing. To demonstrate this, we can use the Debug macro to transmit the current state to the computer and watch what is happening. Program 8-4 is the voltmeter Program (7-1) with some additional debug code. The complete program is in the download file, but reproduced here are the altered lines marked with chevrons. The 25 ms delay macro was added to settle the serial comms as the update rate from the PIC is too fast.

CBLOCK 0x70 TICKS STATE_M0 STATE_M1 STATE_M2 Y0 Y1 Y2 Y3 Xa0 Xa1 Xa2 VALUE FLAGS

; Used to count 8.2ms ticks.

; Output variable for arithmetic. ; Output variable for arithmetic. ; Output variable for arithmetic. ; ; ; ; ; ; ; ; ;

Input variable for arithmetic. Input variable for arithmetic. Input variable for arithmetic. Is the value to be written to the display (10 is blank). The FLAGS bits are: 0 is display update, 1 is division complete, 2 is voltage available, and 3 is reading required. Number of laps with a zero display. Added for debug.