• No results found

Reading฀a฀Relative

Encoder

After฀all฀this฀theory,฀let’s฀look฀

at฀some฀code.฀Program฀6-3฀

will฀read฀a฀16฀position,฀two-bit฀relative฀shaft฀encoder฀(CTS฀

model฀288T232R161A2)฀and฀

illuminate฀an฀LED฀indicating฀

whether฀the฀shaft฀is฀rotated฀

clockwise฀or฀counter-clock-wise.฀We’ll฀also฀increment฀a฀

counter฀for฀clockwise฀rotation฀

and฀decrement฀it฀for฀counter-clockwise฀rotation฀and฀output฀

the฀counter฀value฀over฀the฀

RS-232฀port.฀The฀circuit฀as-sociated฀with฀Program฀6-3฀is฀

shown฀in฀Figure฀6-10.฀

Program฀6-3

;Variables

;---CurVal฀ ฀ Var฀ Byte฀ ;current฀encoder OldVal฀ ฀ Var฀ Byte฀ ;old฀encoder

Counter฀฀ Var฀ Sbyte฀ ;counts฀state฀changes

;Initialize

;---Clear

Output฀A0฀ ;clockwise฀LED

Output฀A1฀ ;counter-clockwise฀LED Low฀A0฀ ฀ ;initialize฀both฀LEDs฀off Low฀A1

EnableHSerial

SetHSerial฀H115200฀ ;change฀if฀lower฀speed฀needed

CurVal฀=฀(PortB฀&฀%00110000)฀>>฀4฀ ;get฀current฀reading

OldVal฀=฀CurVal฀ ;initialize฀for฀future฀change HSerOut฀[“Ctr฀“,SDEC฀Counter,13]฀ ;output฀the฀counter

Main

;---฀฀฀CurVal฀=฀(PortB฀&฀%00110000)฀>>฀4฀ ;returns฀0…3

฀฀฀If฀CurVal฀<>฀OldVal฀Then

฀฀฀฀฀฀Branch฀CurVal,฀[S0,S1,S2,S3]฀ ;if฀changed,฀look฀at฀old฀

฀฀฀EndIf

฀฀฀AfterBranch GoTo฀Main

S0฀ ;reading฀is฀0,฀check฀if฀came฀from฀2฀or฀1

;---฀ If฀OldVal฀=฀2฀Then฀

฀฀฀GoSub฀ClockWise฀

ELSE฀

฀฀฀GoSub฀CounterClockWise

EndIf

Figure฀6-10:฀Schematic฀for฀incremental฀encoder฀read฀program.

GoTo฀AfterBranch

S1฀ ;reading฀is฀1,฀check฀if฀came฀from฀0฀or฀3

;---฀ If฀OldVal฀=฀0฀Then฀

฀฀฀GoSub฀ClockWise฀

ELSE฀

฀฀฀GoSub฀CounterClockWise

EndIf฀

GoTo฀AfterBranch

S2฀ ;reading฀is฀2,฀check฀if฀came฀from฀3฀or฀0

;---฀ If฀OldVal฀=฀3฀Then฀

฀฀฀GoSub฀ClockWise฀

ELSE฀

฀฀฀GoSub฀CounterClockWise

EndIf฀

GoTo฀AfterBranch

S3฀ ;reading฀is฀3,฀check฀if฀came฀from฀1฀or฀2

;---฀ If฀OldVal฀=฀1฀Then฀

฀฀฀GoSub฀ClockWise฀

ELSE฀

฀฀฀GoSub฀CounterClockWise

EndIf

GoTo฀AfterBranch

ClockWise

;---฀ High฀A0฀฀ ;turn฀CW฀LED฀on

Low฀A1฀ ฀ ;turn฀CCW฀LED฀off

OldVal฀=฀CurVal฀ ;ready฀to฀check฀for฀next฀input

Counter฀=฀Counter฀+฀1฀ ;bump฀up฀counter

฀฀฀ HSerOut฀[“Ctr฀“,SDEC฀Counter,13]฀ ;output฀the฀counter Return

CounterClockWise

;---฀ Low฀A0฀ ฀ ;turn฀CW฀LED฀off

High฀A1฀฀ ;turn฀CCW฀LED฀on

OldVal฀=฀CurVal฀ ;ready฀for฀next฀input

Counter฀=฀Counter฀–1฀ ;decrement฀counter

HSerOut฀[“Ctr฀“,SDEC฀Counter,13]฀ ;output฀the฀counter Return

End

Let’s฀go฀through฀the฀interesting฀portions฀of฀the฀code.

;Variables

;---CurVal฀ ฀ Var฀ Byte

OldVal฀ ฀ Var฀ Byte

Counter฀฀ Var฀ SByte

We’ll฀read฀and฀hold฀the฀encoder฀value฀in฀CurVal.฀OldVal฀holds฀the฀previous฀encoder฀value,฀and฀we฀will฀

increment฀or฀decrement฀Counter฀depending฀on฀the฀direction฀of฀shaft฀rotation.฀Counter฀is฀a฀signed฀byte,฀

with฀permitted฀values฀from฀–128฀to฀+127,฀thus฀allowing฀us฀to฀increment฀or฀decrement฀Counter฀from฀its฀

initialized฀value฀of฀0.

CurVal฀=฀(PortB฀&฀%00110000)฀>>฀4 OldVal฀=฀CurVal

We฀initialize฀by฀reading฀the฀current฀encoder฀value฀and฀set฀the฀old฀value฀equal฀to฀the฀current฀value.฀This฀

permits฀us฀to฀detect฀the฀first฀rotation฀by฀checking฀for฀CurVal฀<>฀OldVal.฀First,฀we฀perform฀a฀bitwise฀

AND฀(“&”)฀of฀the฀read฀value฀with฀%00110000.฀The฀AND฀function฀masks฀off฀all฀bits฀read฀on฀PortB฀except฀

for฀those฀corresponding฀to฀1’s฀in฀the฀mask฀operator฀%00110000,฀B5฀and฀B4.฀Masking฀all฀except฀B5฀and฀B4฀

permits฀us฀to฀ignore฀the฀values฀returned฀by฀the฀remainder฀of฀PortB’s฀pins.฀Since฀the฀encoder฀is฀connected฀to฀

B4฀and฀B5,฀the฀read฀value฀is฀shifted฀to฀the฀left฀by฀four฀bits,฀which฀we฀remove฀by฀shifting฀the฀returned฀value฀

four฀bits฀to฀the฀right.฀(The฀reason฀we฀connected฀the฀encoder฀to฀B4฀and฀B5฀instead฀of฀B0฀and฀B1฀will฀be฀seen฀

when฀we฀look฀at฀the฀interrupt฀version฀of฀Program฀6-3.)฀To฀force฀the฀correct฀order฀of฀operation,฀we฀enclose฀

the฀logical฀AND฀operation฀with฀parentheses.฀In฀the฀absence฀of฀the฀parentheses,฀MBasic฀5.3.0.0฀would฀first฀

divide฀%00110000฀by฀16฀and฀then฀logically฀AND฀the฀result฀with฀the฀value฀read฀on฀PortB.฀This฀represents฀a฀

change฀from฀earlier฀versions฀of฀MBasic,฀where฀the฀order฀of฀precedence฀was฀different.

CTS’s฀data฀sheet฀shows฀the฀output฀sequence฀(assuming฀clockwise฀rotation)฀for฀the฀first฀eight฀positions฀as:

Contact฀Closure To฀Common฀

B 0 0 1 1 0 0 1 1

A 0 1 1 0 0 1 1 0

Position฀No. 0 1 2 3 4 5 6 7

In฀our฀circuit฀a฀contact฀closure฀(data฀sheet฀1)฀results฀in฀a฀logical฀low฀at฀the฀PIC’s฀input฀pin,฀and฀an฀open฀(data฀

sheet฀0)฀is฀a฀logical฀high.฀Hence,฀our฀circuit฀inverts฀the฀0’s฀and฀1’s฀from฀the฀CTS’s฀data฀sheet’s฀prospective.฀

As฀shown฀in฀Figure฀6-10,฀pin฀A฀is฀connected฀to฀B4,฀while฀pin฀B฀is฀connected฀to฀B5.฀Consequently,฀our฀PIC฀

input฀pin฀sequence฀looks฀like:

PIC฀Pin B5 1 1 0 0 1 1 0 0

B4 1 0 0 1 1 0 0 1

Position฀No. 0 1 2 3 4 5 6 7

PortB฀&฀b00110000 48 32 0 16 48 32 0 16

PortB฀&฀b00110000฀/฀16 3 2 0 1 3 2 0 1

We฀now฀rewrite฀(PortB฀&฀b00110000)฀>>฀4฀in฀two฀rows,฀representing฀clockwise฀and฀counter-clockwise฀

rotation,฀starting฀with฀0฀for฀convenience:

Clockwise 0 1 3 2 0 1 3 2

Counter-Clockwise 0 2 3 1 0 2 3 1

To฀understand฀how฀we฀differentiate฀clockwise฀and฀counter-clockwise฀rotation,฀suppose฀the฀encoder’s฀current฀

value฀is฀read฀as฀0.฀If฀the฀previous฀value฀was฀0,฀the฀encoder฀shaft฀has฀not฀moved฀since฀its฀last฀read.฀If฀the฀pre-vious฀value฀was฀2,฀the฀rotation฀is฀clockwise;฀if฀the฀previous฀value฀was฀1,฀the฀rotation฀is฀counter-clockwise.฀

Main

;---฀฀฀CurVal฀=฀(PortB฀&฀%00110000)฀>>฀4

฀฀฀If฀CurVal฀<>฀OldVal฀Then

฀฀฀฀฀฀Branch฀CurVal,฀[S0,S1,S2,S3]

฀฀฀EndIf

฀฀฀AfterBranch GoTo฀Main

With฀this฀understanding,฀the฀main฀program฀should฀be฀clearer.฀We฀have฀two฀sequential฀comparisons฀to฀make.฀

First,฀has฀the฀encoder฀moved,฀which฀we฀test฀for฀with฀the฀If฀CurVal฀<>฀OldVal฀Then… ฀test.฀If฀the฀en-coder฀shaft฀has฀rotated฀since฀its฀last฀read,฀the฀conditional฀is฀true฀and฀we฀branch฀to฀one฀of฀four฀program฀labels฀

based฀on฀the฀current฀encoder฀value,฀S0฀if฀the฀value฀is฀0,฀and฀so฀on.฀

S0฀ ;reading฀is฀0,฀check฀if฀came฀from฀2฀or฀1

;---฀ If฀OldVal฀=฀2฀Then฀

฀฀฀GoSub฀ClockWise฀

ELSE฀

฀฀฀GoSub฀CounterClockWise

EndIf

GoTo฀AfterBranch

Program฀execution฀reaches฀S0฀only฀if฀CurVal฀=฀0.฀Hence,฀to฀determine฀the฀rotation฀direction,฀we฀only฀

need฀test฀the฀prior฀value,฀which฀we฀do฀with฀the฀If฀OldVal฀=฀2฀test.฀If฀true,฀the฀direction฀is฀clockwise฀and฀

we฀execute฀the฀ClockWise฀subroutine;฀if฀false,฀the฀direction฀must฀be฀counter-clockwise,฀so฀we฀execute฀the฀

CounterClockWise฀subroutine.

ClockWise

;---฀ High฀A0฀฀ ;turn฀CW฀LED฀on

Low฀A1฀ ฀ ;turn฀CCW฀LED฀off

OldVal฀=฀CurVal฀ ;ready฀to฀check฀for฀next฀input

Counter฀=฀Counter฀+฀1฀ ;bump฀up฀counter

HSerOut฀[“Ctr฀“,SDEC฀Counter,13]฀ ;output฀the฀counter Return

CounterClockWise

;---฀ Low฀A0฀ ฀ ;turn฀CW฀LED฀off

High฀A1฀฀ ;turn฀CCW฀LED฀on

OldVal฀=฀CurVal฀ ;ready฀for฀next฀input

Counter฀=฀Counter฀–1฀ ;decrement฀counter

HSerOut฀[“Ctr฀“,SDEC฀Counter,13]฀ ;output฀the฀counter Return

The฀ClockWise฀and฀CounterClockWise฀subroutines฀are฀mirror฀images฀of฀each฀other;฀they฀set฀and฀clear฀

the฀direction฀LEDs฀and฀bump฀up฀or฀decrement฀the฀counter฀and฀write฀the฀updated฀value฀of฀the฀position฀coun-ter฀to฀the฀hardware฀serial฀port.

After฀reading฀Chapter฀4,฀you฀may฀wonder฀where฀the฀debounce฀routine฀is.฀After฀all,฀we฀are฀sensing฀a฀mechan-ical฀switch฀whose฀data฀sheet฀quotes฀a฀5฀ms฀maximum฀bounce฀period.฀The฀answer฀is฀that฀the฀execution฀time฀

of฀the฀program,฀including฀the฀serial฀output฀statement,฀provides฀sufficient฀delay฀to฀provide฀trouble฀free฀read-ing.฀In฀most฀cases,฀a฀real฀program฀would฀branch฀into฀enough฀code฀that฀consumed฀enough฀time฀to฀provide฀

debounce฀as฀an฀incidental฀benefit.฀If฀not,฀we฀could฀add฀a฀Pause฀5฀statement฀after฀each฀switch฀read.฀Or,฀we฀

might฀add฀debounce฀circuitry฀to฀our฀design.

As฀it฀stands฀now,฀with฀a฀20฀MHz฀clock,฀Program฀6-3’s฀main฀program฀loop฀executes฀in฀400฀µs฀where฀the฀

encoder฀has฀not฀changed฀value,฀and฀in฀750฀µs฀where฀it฀has฀changed฀value.฀Thus,฀it฀accurately฀reads฀well฀over฀

1,000฀steps/second,฀or฀3700฀RPM,฀far฀faster฀than฀someone฀twisting฀a฀knob฀attached฀to฀the฀shaft฀will฀operate฀

the฀encoder.฀

Figures฀6-11฀and฀6-12฀show฀the฀input฀waveforms฀as฀I฀turned฀the฀shaft฀quickly฀by฀hand.฀Incidentally,฀you฀

may฀read฀references฀to฀quadrature฀encoder฀outputs฀being฀read฀by฀comparing฀the฀phase฀of฀the฀two฀outputs฀for฀

clockwise฀and฀counter-clockwise฀rotation.฀Figure฀6-11฀shows฀Channel฀2฀(B5)฀leading฀Channel฀1฀(B4)฀during฀

clockwise฀rotation,฀while฀Figure฀6-12฀shows฀Channel฀1฀(B4)฀leads฀Channel฀2฀(B5)฀during฀counter-clockwise฀

rotation.฀And,฀in฀fact,฀the฀waveforms฀at฀B4฀and฀B5฀are฀90°฀out฀of฀phase.฀I฀view฀this฀phase฀comparison฀analy-sis฀far฀more฀confusing฀than฀beneficial,฀particularly฀in฀a฀beginning฀level฀discussion.฀Hence฀we฀have฀instead฀

focused฀on฀reading฀the฀encoder฀value฀and฀comparing฀it฀with฀the฀prior฀value.฀

Program฀6-3฀spends฀a฀great฀deal฀of฀time฀potentially฀doing฀nothing;฀in฀a฀typical฀program฀that฀uses฀a฀rotary฀

encoder฀to฀select฀menu฀items,฀operator฀interaction฀is฀infrequent.฀How฀many฀times฀do฀you฀change฀the฀ther-mostat฀setting฀in฀your฀house?฀Certainly฀not฀hundreds฀of฀times฀a฀second.฀Wouldn’t฀it฀be฀nice฀if฀we฀our฀main฀

program฀could฀spend฀its฀time฀making฀important฀computations฀and฀branch฀to฀deal฀with฀a฀changed฀user฀input฀

only฀when฀the฀encoder฀is฀changed,฀without฀checking฀the฀encoder’s฀value฀every฀time฀we฀go฀through฀our฀main฀

loop?฀The฀answer,฀of฀course,฀is฀that฀not฀only฀would฀this฀be฀nice,฀the฀folks฀at฀Microchip฀and฀Basic฀Micro฀have฀

come฀up฀with฀a฀way฀to฀do฀exactly฀that฀through฀the฀mechanism฀of฀interrupts.฀We’re฀going฀to฀save฀interrupts฀

until฀Chapter฀10,฀but฀here’s฀a฀sneak฀preview.

Whenever฀the฀state฀of฀input฀pins฀RB4…RB7฀change,฀we฀can฀cause฀the฀PIC฀to฀stop฀whatever฀code฀it฀is฀run-ning฀and฀instead฀run฀new฀code.฀This฀process฀is฀called฀an฀“interrupt”฀in฀computer-speak฀and฀it฀does฀exactly฀

that;฀it฀interrupts฀the฀current฀code฀and฀switches฀execution฀to฀an฀“interrupt฀handler.”฀

Delete฀the฀Code฀Below฀from฀Program฀6-3 Replace฀it฀with฀the฀Following฀Interrupt฀Version CurVal฀=฀(PortB฀&฀%00110000)฀>>฀4

OldVal฀=฀CurVal฀

Main

;---฀฀฀CurVal฀=฀(PortB฀&฀%00110000)>>4฀

฀฀฀฀฀฀;returns฀0…3

฀฀฀฀฀฀If฀CurVal฀<>฀OldVal฀Then

฀฀฀฀฀฀Branch฀CurVal,฀[S0,S1,S2,S3]฀

฀฀฀฀฀฀;if฀changed,฀look฀at฀old฀

฀฀฀EndIf

฀฀฀AfterBranch GoTo฀Main

CurVal฀=฀(PortB฀&฀%00110000)฀>>฀4 OldVal฀=฀CurVal

OnInterrupt฀RBINT,฀ReadEncoder Enable฀RBINT฀

Main

;---GoTo฀Main

Disable ReadEncoder

CurVal฀=฀(PortB฀&฀%00110000)>>4

฀฀฀฀฀฀฀If฀CurVal฀<>฀OldVal฀Then

฀฀฀฀฀฀Branch฀CurVal,฀[S0,S1,S2,S3]

฀฀฀EndIf

฀฀฀AfterBranch Resume

Figure฀6-11:฀Clockwise฀manual฀rotation฀Ch1:฀

B4฀Ch2:฀B5.

Figure฀6-12:฀Counter-clockwise฀manual฀rotation฀

Ch1:฀B4฀Ch2:฀B5.

Whenever฀any฀pin฀of฀RB4…RB7฀changes฀state฀(from฀0฀to฀1฀or฀vice฀versa),฀if฀enabled,฀an฀RBINT ฀type฀inter-rupt฀is฀issued.฀To฀use฀an฀interrupt฀in฀MBasic,฀we฀first฀associate฀the฀interrupt฀type฀with฀the฀program฀label฀

of฀the฀interrupt฀handler฀with฀the฀OnInterrupt฀RBINT,฀ReadEncoder฀procedure.฀This฀simply฀says฀that฀

whenever฀an฀RBINT฀is฀triggered,฀we฀execute฀code฀following฀the฀label฀ReadEncoder.฀Next,฀we฀turn฀on฀or฀

“enable”฀the฀RBINT฀interrupt฀with฀the฀procedure฀Enable฀RBINT.฀Now,฀whenever฀we฀turn฀the฀encoder฀

shaft,฀either฀B4฀or฀B5฀will฀change฀value฀and฀program฀execution฀will฀jump฀to฀ReadEncoder.

Disable ReadEncoder

CurVal฀=฀(PortB฀&฀%00110000)฀>>4

฀If฀CurVal฀<>฀OldVal฀Then

฀฀฀฀฀฀Branch฀CurVal,฀[S0,S1,S2,S3]

฀฀฀EndIf

฀฀฀AfterBranch Resume

In฀order฀to฀avoid฀interrupts฀of฀interrupts,฀we฀must฀turn฀off฀additional฀interrupts฀before฀the฀interrupt฀handler฀

via฀the฀Disable฀function.฀The฀interrupt฀procedure฀itself฀duplicates฀the฀code฀we฀developed฀in฀Program฀6-3.฀

At฀the฀end฀of฀the฀interrupt฀procedure,฀we฀return฀to฀normal฀program฀flow฀through฀a฀Resume฀statement.

The฀code฀executed฀in฀the฀Main฀…฀GoTo฀Main฀loop฀in฀our฀interrupt฀program฀is฀simply฀a฀serial฀output.฀But,฀

it฀could฀have฀been฀anything,฀such฀as฀lengthy฀mathematical฀calculations,฀LCD฀writes,฀or฀anything฀else฀you฀

can฀do฀in฀MBasic.฀When฀the฀interrupt฀occurs,฀MBasic฀will฀seamlessly฀execute฀the฀interrupt฀handler฀and฀

return฀flow฀to฀your฀main฀program.฀Hence,฀it฀is฀up฀to฀your฀code฀to฀recognize฀and฀accommodate฀changes฀to฀

Counter—as฀a฀result฀of฀user฀input฀commands—between฀program฀statements.฀

There’s฀another฀subtlety฀here฀as฀well.฀MBasic฀will฀not฀break฀execution฀of฀a฀statement฀to฀service฀an฀interrupt.฀

Suppose,฀for฀example,฀your฀main฀code฀has฀a฀one฀second฀delay฀in฀it฀through฀a฀Pause฀1000฀procedure.฀฀If฀

you฀twist฀the฀shaft฀on฀the฀encoder฀just฀as฀the฀Pause฀1000฀procedure฀starts฀execution,฀the฀interrupt฀handler฀

will฀not฀be฀invoked฀until฀after฀the฀

Pause฀1000฀completes.฀So,฀if฀this฀

happens฀you฀may฀twist฀the฀shaft฀

encoder฀knob฀through฀a฀dozen฀posi-tions,฀but฀the฀interrupt฀procedure฀

won’t฀recognize฀them.฀฀(In฀a฀later฀

chapter฀we’ll฀later฀see฀that฀an฀as-sembler฀language฀interrupt฀doesn’t฀

share฀this฀problem.)