21.12.2013 Views

Maskinnära programmering 6B2266

Maskinnära programmering 6B2266

Maskinnära programmering 6B2266

SHOW MORE
SHOW LESS

You also want an ePaper? Increase the reach of your titles

YUMPU automatically turns print PDFs into web optimized ePapers that Google loves.

Maskinnära <strong>programmering</strong> <strong>6B2266</strong><br />

Häfte: Interna enheter.<br />

Timers, CCP, Interrupt, AD-omvandlare.<br />

Programexempel för 16F84/16F628/16F874.<br />

• Anslutning av extern LCD-display<br />

• Avläsning av rotationspulsgivare<br />

• Interrupt, INT-interruptet, RB-interruptet<br />

• Timer0, Timer1 och Timer2<br />

• CCP-enheterna, Capture Compare (PWM)<br />

• Ex. Tändpunktsförställning hos förbränningsmotor.<br />

• CCP-Capture, frekvensmätning.<br />

• PWM, Pulse Width Modulation, pulsbreddsmodulering<br />

• PWM-lab, Varvtalsratt till LEGO-motorn, varvtal/riktningsratt till LEGOmotorn<br />

• Programmering av TIMER2 som samplingsklocka<br />

• PIC16F874 AD-omvandlare<br />

© 2005 William Sandqvist


LCD-display 16×1 egentligen (2×8)×1<br />

GTC-1601 TR6N0C ELFA 75-511-53<br />

GTC-1601 ser ut som en enkelradig display med 16 tecken,<br />

med består i själva verket av två rader om 8 tecken som placerats efter varandra.<br />

Bekvämt om man ska skriva ut tabeller, men besvärligt om det gäller långa meningar.<br />

( Se programexemplet ).<br />

Detta är ett exempelprogram med ELFA’s billigaste LCD-display (c:a 100:-).<br />

LCD-displayer finns av många olika typer. Kanske köper Du själv en för användning<br />

till din <strong>programmering</strong>suppgift?<br />

De flesta LCD-displayer använder samma styrkrets och därför kan nedanstående<br />

programexempel användas som en utgångspunkt även för andra displayer.<br />

Man måste emellertid vara beredd på att läsa ”manualen” för displayen, och att det<br />

kan behövas göra en del anpassningar av programmet.<br />

LCD-displayernas kontaktstandard<br />

Pin nr. Namn Funktion Pin nr. Namn Funktion<br />

1 V SS Gnd, jord 8 D1 (databit 1)<br />

2 V DD +5V, matningsp. 9 D2 (databit 2)<br />

3 V EE Kontrast 10 D3 (databit 3)<br />

4 RS Char/!Command 11 D4 databit 4<br />

5 RD/!WR Read/!Write 12 D5 databit 5<br />

6 E Enable, clock 13 D6 databit 6<br />

7 D0 (databit 0) 14 D7 databit 7<br />

Att mata spädbarn ...<br />

Att mata spädbarn går lättare när man ger smakbitar (nibble).<br />

Eftersom två smakbitar blir till en munsbit (byte), kan barnet ändå<br />

bli mätt till slut.<br />

När det gäller små PIC-processorer har man ofta ont om pinnar, och man vill då<br />

ogärna ansluta en LCD-display med många ledningar. Alla LCD-displayer har därför<br />

en finess som gör att tecken och kommandon kan "matas in" med 2 st 4-bitarstal<br />

(nibble) i följd, i stället för med ett 8-bitarstal (byte). I så fall räcker det med att man<br />

ansluter 4 dataledningar och 2 styrledningar ( om man avstår från att kunna läsa i<br />

LCD-kretsens register, med RD/!WR ).<br />

Det kommando som "ställer in" om 8 eller 4 dataledningar ska användas måste ges<br />

först. Detta kommando har naturligtvis valts så att 4 bitar räcker för att skilja det från<br />

de övriga kommandona!<br />

3


PIC-processorn har en instruktion för att byta plats på två nibble i en byte<br />

SWAPF f, d. Kompilatorn Cc5x har en inbyggd funktion för samma sak<br />

x = swap(x);. Instruktionen finns med för att underlätta hantering av 4-bitsdata.<br />

I programexemplet nedan plockas i stället nibblen ut "bit för bit". Det går då åt lite<br />

fler instruktioner än om man använder swap, men programmet blir enklare att<br />

anpassa ifall man skulle behöva koppla displayen till andra av PIC-processorns<br />

pinnar.<br />

Koppla så här ...<br />

Kopplingsdäcket med 16x1-display. Även seriekommunikationskretsen får plats. ( Den används inte av<br />

exempelprogrammet).<br />

OBS! Viktigt! Avstörningskondensatorn 470 µF ELFA 67-011-48.<br />

( LCD-displayen drar nämligen en hel del ström … )<br />

4


Testprogram lcd16x1 "Hello world! "<br />

/* lcd16x1.c LCD-display 4 bit data (nibble) conn. to PIC 16F628 */<br />

/* GTC-1601-TR6N0C 1 (2) line 2x8 example: "Hello wo" + "rld! "*/<br />

/* ELFA 75-511-53 c:a 110:- */<br />

/* Internal ic: KS0066U used pins: D4-D7, RS, EN, RD/!WR=0 */<br />

/*<br />

________ ________<br />

| \/ | RD/!WR = 0<br />

D6--|RA2 16F628 RA1|--D5 (only write, wait while busy)<br />

D7--|RA3<br />

RA0|--D4<br />

|RA4 RA7/OSC1|<br />

|RA5/MCLR RA6/OSC2|<br />

+5V--|Vss<br />

Vdd|--GND<br />

|RB0/INT (RB7)/PGD|<br />

|RB1/Rx (RB6)/PGC|<br />

|RB2/Tx<br />

RB5|--RS<br />

EN--|RB3/CCP (RB4)/PGM|<br />

|__________________|<br />

*/<br />

#include "16F628.h"<br />

#pragma config |= 0x3ff0<br />

// use internal 4MHz oscillator<br />

/* I/O-pin definitions */<br />

/* change if you need a pin for a different purpose */<br />

#pragma bit RS @ PORTB.5<br />

#pragma bit EN @ PORTB.3<br />

#pragma bit D7 @ PORTA.3<br />

#pragma bit D6 @ PORTA.2<br />

#pragma bit D5 @ PORTA.1<br />

#pragma bit D4 @ PORTA.0<br />

#include "delays.c"<br />

void delay( char ); // ms delay function from file delays.c<br />

void lcd_init( void );<br />

void lcd_putchar( char );<br />

char text1( char );<br />

char text2( char );<br />

void main( void)<br />

{<br />

CM0=1; CM1=1; CM2=1; // no comparators on PORTA pins<br />

/* I/O-pin direction in/out definitions, change if needed */<br />

TRISB = 0b011.0.1.0.111; // output to RS and EN<br />

TRISA = 0b1111.0000; // output to D4-D7<br />

char i;<br />

lcd_init();<br />

RS = 1; // LCD in character-mode<br />

// display the 8 char text1() sentence<br />

for(i=0; i


* ****************** FUNCTIONS ****************************** */<br />

char text1( char x) // this is the way to store a sentence<br />

{ skip(x); /* internal function CC5x. */<br />

#pragma return[] = "Hello wo" // 8 chars max!<br />

}<br />

char text2( char x) // this is the way to store a sentence<br />

{ skip(x); /* internal function CC5x. */<br />

#pragma return[] = "rld! " // 8 chars max!<br />

}<br />

void lcd_init( void ) // must be run once before using the display<br />

{ delay(40); // give LCD time to settle<br />

/* Function set */<br />

RS = 0; // LCD in command-mode<br />

// 0010.xxxx 4-bit mode<br />

// 0010.xxxx 4-bit mode<br />

lcd_putchar(0b0010.0010);<br />

// 0010.xxxx 4-bit mode three times!<br />

// The display is actual two lines after each other<br />

// 1.0.xxxxxx two line.display off.xxxxxx<br />

lcd_putchar(0b0010.1.0.00);<br />

/* Display ON/OFF Control */<br />

// 0000.xxxx<br />

// 1.1.0.0.xxxx 1.display on.cursor off.blink off.xxxx<br />

lcd_putchar(0b0000.1.1.0.0);<br />

/* Display clear */<br />

// 0000.xxxx<br />

// 0001.xxxx<br />

lcd_putchar(0b0000.0001);<br />

/* Entry mode set */<br />

// 0000.xxxx<br />

// 01.1.0.xxxx 01.increment mode.shift off.xxxx<br />

lcd_putchar(0b0000.01.1.0);<br />

// initialization is done!<br />

}<br />

void lcd_putchar( char data )<br />

{ // must set LCD-mode before calling this function!<br />

// RS = 1 LCD in character-mode<br />

// RS = 0 LCD in command-mode<br />

// upper Nibble<br />

D7 = data.7;<br />

D6 = data.6;<br />

D5 = data.5;<br />

D4 = data.4;<br />

EN = 0;<br />

EN = 1;<br />

delay(5);<br />

// lower Nibble<br />

D7 = data.3;<br />

D6 = data.2;<br />

D5 = data.1;<br />

D4 = data.0;<br />

EN = 0;<br />

EN = 1;<br />

delay(5);<br />

}<br />

6


Lösta programexempel med PIC16F628<br />

Avläsning av pulsgivare<br />

Pulsgivare ELFA 35-847-60<br />

Rotations-Pulsgivare har länge använts som digitala vinkelgivare i industrin, och de<br />

används numera även som inställningsrattar och vred i hemelektronik ( tex. för Jog<br />

up/down ).<br />

De senare typerna har mekaniska kontakter och masstillverkas till låga priser ( det<br />

finns pulsgivare från c:a 20:- ), så det finns all anledning att bekanta sig med<br />

givartypen.<br />

Givaren består av släpkontakter och en kodskiva. Den har tre anslutningar A B och C<br />

(Common). Vid vridning blir det kontakt mellan A-C, eller B-C, eller med både A-C<br />

B-C. Givaren spänningssätts genom att C-kontakten ansluts till jord och A och B -<br />

kontakterna via var sin "pullup"-resistor ( tex 10 kΩ ) till matningsspänningen 5 V.<br />

Utsignalen från givaren är en tvåbitars Gray-kod som för varja "klick" följer en<br />

sekvens bestående av fyra steg.<br />

ClockWise rotation -><br />

00 01 11 10 00<br />


Det finns flera sätt att avläsa pulsgivaren på. Antingen avsöker man givaren så ofta att<br />

man inte riskerar att missa någon tillståndsändring, eller så låter man alla<br />

tillståndsförändringar generera avbrott, och låter avbrottsrutinen avläsa givaren.<br />

PIC-processorer har en speciell finess. Man kan programera dem att generera avbrott<br />

vid varje förändring på någon av fyra fyra portpinnar RB4 ... RB7. Denna finess<br />

används bland annat till datormöss, som ju har två pulsgivare ( för x-led och y-led )<br />

och därmed fyra signaler.<br />

(Observera! man bör undvika att använda PORTB till något annat om man utnyttjar<br />

denna finess).<br />

Vid avläsningen av givaren ser man till att lagra det föregående tillståndet för att<br />

kunna jämföra detta med det nuvarande tillståndet.<br />

Varje pil i tillståndsdiagrammet består av ett sådant tillståndspar old.new.<br />

Ett enkelt sätt att avläsa givaren är att räkna upp positionen vid pilen 00.01 och ned<br />

positionen vid pilen 01.00. Även om kontakten studsar blir "nettoresultatet" det<br />

rätta, eftersom man ju alltid måste gå den ena vägen en gång mer än den andra för att<br />

byta tillstånd.<br />

Enkoderavläsningsprogram<br />

quad_A och quad_B är bitvariabler för de portpinnar A och B signalen anslutits till.<br />

Det avlästa tillståndet och föregående tillstånd samlas i fyra bitar i variabeln trans<br />

( transition, övergång ). Här får man god användning för att kompilatorn Cc5x har<br />

bitoperationer ( som tillägg till ANSI-C ). Cc5x har även binära konstanter och<br />

eftersom det är tillåtet att att avgränsa grupper av bitar med punkt-tecken så blir det<br />

enkelt och tydligt att ange vilkor för de olika pilarna ( transitions ) i<br />

tillståndsdiagrammet, tex. som if( trans == 0b00.01 ).<br />

8


while(1)<br />

{<br />

/* read encoder new value */<br />

trans.0 = quad_A;<br />

trans.1 = quad_B;<br />

}<br />

/* compare with old value */<br />

if( trans == 0b00.01 ) counter ++;<br />

if( trans == 0b01.00 ) counter --;<br />

delay(10);<br />

/* replace old value with new value */<br />

trans.2 = trans.0;<br />

trans.3 = trans.1;<br />

Enkoderavläsning. Testprogrammet quad62x.c<br />

Inkoppling av pulsgivarkontakterna<br />

När man kopplar in kontakter till portpinnar behöver man i allmänhet sk. "pullup"-<br />

motstånd. En sluten kontakt kan ge "0"-nivån, men när kontakten är öppen blir nivån<br />

obestämd utan pullup. PIC-processorerna har inbyggda "pullup"-motstånd ( c:a 10k )<br />

som kopplas in med kommandot:<br />

OPTION.7 = 0; /* internal pullup resistors is on */.<br />

I figuren finns också två 1k serie-resistorer för att "isolera" pulsgivaren från<br />

kretsprogrammeraren. Vid krets<strong>programmering</strong> används ju pinnarna RB6 och RB7<br />

( om vi inte hade haft behov av att omprogrammera PIC-kretsen hade dessa pinnar<br />

varit helt lediga och serieresistorerna onödiga ).<br />

I det följande programmet quad62x.c läses pulsgivaren av med hjälp av PCprogrammet<br />

Hyperterminalen.<br />

Därför tas funktionsfiler som innehåller initserial, putchar, string_out, inttoa och<br />

delay med i arbetsbiblioteket.<br />

9


* quad62x.c read rotary encoder with PC-terminal program */<br />

/* SERIAL COMMUNICATION (RS232) 9600 baud. */<br />

#include "16f628.h"<br />

#include "Math16.h"<br />

#pragma config |= 0x3ff0<br />

char s[11]; /* global string buffer */<br />

/* function prototypes */<br />

void initserial( void ); /* in seriF62x.c */<br />

bit putchar( char ); /* in seriF62x.c */<br />

void string_out( void ); /* in stringIO.c */<br />

void inttoa( int ); /* in ascnum.c */<br />

void delay( char ); /* in delays.c */<br />

#pragma bit quad_A @ PORTB.6<br />

#pragma bit quad_B @ PORTB.7<br />

#include "seriF62x.c"<br />

#include "stringIO.c"<br />

#include "ascnum.c"<br />

#include "delays.c"<br />

void main(void)<br />

{<br />

TRISB.6 = 1; /* quad_A is input */<br />

TRISB.7 = 1; /* quad_B is input */<br />

char trans; /* to store transition */<br />

trans = 0;<br />

int counter;<br />

counter = 0;<br />

delay( 10 );<br />

initserial(); /* Initialise serial port */<br />

putchar('\r');<br />

putchar('\n');<br />

while(1)<br />

{<br />

/* read encoder new value */<br />

trans.0 = quad_A;<br />

trans.1 = quad_B;<br />

/* compare with old value */<br />

if( trans == 0b00.01 ) counter ++;<br />

if( trans == 0b01.00 ) counter --;<br />

/* this is the delay between encoder readings */<br />

inttoa( counter );<br />

string_out();<br />

putchar('\r'); /* Write on the same line! */<br />

}<br />

}<br />

/* replace old value with new value */<br />

trans.2 = trans.0;<br />

trans.3 = trans.1;<br />

Ta med filerna: seriF62x.c, stringIO.c, ascnum.c, delays.c<br />

10


Interrupt, avbrottsprogramering av PIC<br />

midrangekretsarna (ex. med 16F87x)<br />

Interuptlogiken för 16F87x med 15 interuptkällor.<br />

Interruptlogiken för 16F62x med 10 interruptkällor.<br />

Interrupt och Polling<br />

Antag att Du sitter i en skön fåtölj och läser en bok. Plötsligt blir Du avbruten av att<br />

telefonen ringer, Du markerar med en blyertspenna var i boken Du befann dig och<br />

svarar.<br />

Under samtalet ringer det på dörrklockan och Du ber den Du talar med i telefonen<br />

med att dröja kvar medan Du går till dörren. När Du är färdig med ärendet vid dörren<br />

återupptar Du telefonsamtalet. När Du efter ett tag har talat färdigt i telefonen och<br />

avslutat telefonsamtalet kan Du återvända till fåtöljen och fortsätta med att läsa den<br />

goda boken - vid blyertsmärket.<br />

11


Denna scen avser att belysa hur ett huvudprogram ( Du läser en bok ) via en<br />

händelse ( telefonen ringer ) som genererar ett interrupt ( avbrott ) blir avbrutet så att<br />

ett annat program, avbrottsprogrammet, utför en lämplig avbrottsrutin ( Du talar i<br />

telefon ).<br />

Avbrottsprogrammet blir i sin tur avbrutet och får då i stället utföra en annan<br />

avbrottsrutin ( att ta ärendet vid ytterdörren ).<br />

Om interruptmöjligheten inte fanns, om telefonen saknade ringsignal och ytterdörren<br />

saknade dörrklocka, skulle Du gång på gång "oroligt" behöva avbryta läsningen av<br />

boken för att lyfta telefonluren och fråga "är det någon där?", "är det någon där?",<br />

och att öppna ytterdörren för att se efter om någon står utanför och väntar?<br />

Detta kallas för Polling, avsökning.<br />

Det är lätt att inse att Interrupt många gånger är att föredra framför Polling. De allra<br />

flesta mikrostyrkretsar har därför försetts med mer eller mindre utvecklade<br />

mekanismer för interrupt.<br />

Global och Local enable<br />

Om Du vill läsa din bok i lugn och ro utan att bli avbruten kan Du sätta på dig<br />

öronproppar. Telefonen eller dörrklockan kan då ringa hur mycket som helst utan att<br />

bli "betjänade". Du har omöjliggjort interrupt, Disable interrupt. Tar Du bort<br />

öronpropparna har Du åter möjliggjort interrupt, Enable interrupt. Detta kallas för<br />

att på global nivå enable/disable interrupt, Global enable.<br />

Du har även möjlighet att på lokal nivå enabla/disabla interrupt, Local enable. Till<br />

exempel kan Du på lokal nivå disabla telefonen genom att dra ur telefonjacket. Då<br />

kommer Du fortfarande att kunna höra dörrklockan.<br />

( detta målande exempel har jag lånat av en kollega, Robert Meyer )<br />

PIC-processorernas interruptmekanismer<br />

Interruptkällor, globalt, regionalt(?) och lokalt enable<br />

PIC16F87x processorerna har upp till 15 olika interruptkällor. PIC16F628 har tio. De<br />

enablas lokalt med "flaggor" som finns i registren INTCON, PIR1 och PIR2.<br />

Eftersom de alla finns definierade som bitvariabler i respektive processors includefil<br />

så behöver man inte hålla reda på i vilket register och i vilken bitposition de olika<br />

flaggorna finns.<br />

Den lokala enablebiten heter som källan, men med tillägg av bokstaven E på slutet<br />

( tex. external interrupt INT har enableflaggan INTE ).<br />

Interrupt enablas globalt med flaggan GIE ( Global interrupt enable ) som finns i<br />

registret INTCON. Vid start har processorn interrupt disablat.<br />

I INTCON finns även en flagga PEIE ( Peripheral interrupt enable ) som enablar<br />

registren PIE1 och PIE2. Man kanske kan kalla detta för "regionalt enable"?<br />

Observera således att tex CCP1E ( i PIE1 ) sammanlagt enablas på tre ställen.<br />

1) CCP1E=1;, 2) PEIE=1; och 3) GIE=1;.<br />

12


Några interruptkällor<br />

Namn Register Kommentar<br />

INT INTCON External interrupt, RB0/INT-pinnen<br />

TMR0 INTCON TMR0 Overflow interrupt<br />

RBI INTCON interrupt on change PORTB RB.0...RB.3<br />

ADI PIE1 AD-converter interrupt (PIC16F87x)<br />

TXI PIE1 USART Transmitter interrupt<br />

RCI PIE1 USART Reciever interrupt<br />

CCP1I PIE1 CCP1 Capture/Match interrupt<br />

CCP2I PIE2 CCP2 Capture/Match interrupt (PIC16F87x)<br />

TMR2I PIE1 TMR2 PR2 Match interrupt<br />

TMR1I PIE1 TMR1 Overflow interrupt<br />

Interruptrutin<br />

När en interruptkälla, som är enablad, begär interrupt så anropas interruptrutinen.<br />

Denna har alltid en fast adress i programminnet ( 0x0004 ). För att vara säker på att<br />

interruptrutinen får rätt adress skriver man den först, före main():<br />

Med raden interrupt int_server( void ) ger vi interruptrutinen namnet<br />

int_server och definierar den på samma sätt som en vanlig funktion.<br />

#include "16F874.h"<br />

// #include "16F628.h"<br />

#include "int16Cxx.h"<br />

#pragma origin 4<br />

interrupt int_server( void )<br />

{<br />

int_save_registers<br />

/* interrupt routine */<br />

int_restore_registers<br />

}<br />

void main( void )<br />

{<br />

/* the main program */<br />

/* local enable */<br />

/* global enable */<br />

/* something that causes the interrupt */<br />

}<br />

I interruptrutinen gäller automatiskt att fortsatta interrupt är disablade, GIE=0;. ( Om<br />

man skriver GIE=1; inuti interruptrutinen, kan nya händelser även avbryta<br />

interruptrutinen, så gör inte detta ).<br />

Till skillnad mot en vanlig funktion så vet man aldrig var i huvudprogrammet man<br />

befinner sig när interruptet sker och interruptrutinen anropas. Interruptet kanske sker<br />

mitt i ett vilkorsuttryck. När huvudprogrammet sedan får tillbaka kontrollen från<br />

interruptrutinen, och får chansen att utvärdera vilkorsuttrycket, så vill det till att<br />

interuptrutinen inte har förändrat några flaggor eller register!<br />

13


Det är därför viktigt att interruptrutinen börjar med att lagra undan innehållet W-<br />

registret mfl. på säker plats innan rutinen utför något som kan påverka dessa. Det sker<br />

här genom att man includerat filen #include "int16Cxx.h" och skrivit<br />

int_save_registers.<br />

Denna textsträng är ett uttryck som C:s preprocessor "byter ut" mot några, för<br />

processortypen, lämpliga instruktioner. Det är därför viktigt att man anger<br />

processortypen med #include "16F874.h" (#include "16F628.h")<br />

i början av programmet.<br />

Genom att avsluta interruptrutinen med textsträngen<br />

int_restore_registers<br />

så läggs de ursprungliga registerinnehållen tillbaka innan återhoppet till<br />

huvudprogrammet sker.<br />

Väl tillbaka i huvudprogrammet blir interrupt åter enablat.<br />

Interrupt flag bits<br />

Registren INTCON, PIR1 och PIR2 innehåller olika interruptflagbits. Bitarna heter<br />

som källan, men med tillägg av bokstaven F på slutet. Om en interruptflagbit är "1",<br />

så innebär det att motsvarande händelse har inträffat.<br />

Det är lämpligt att börja interruptrutinen med att undersöka vilken händelse det är<br />

som utlöst interruptet, för att sedan styra programflödet till rätt åtgärd. Observera att<br />

man måste 0-ställa aktuell interruptflagbit innan man lämnar interruptrutinen.<br />

Annars kommer man direkt tillbaka till interruptrutinen! ( dvs. programmet låser sig ).<br />

Även när man inte använder interrupt, när interruptet är disablat, kan programmet<br />

ändå avkänna dessa bitar, dvs "polla" bitarna.<br />

Bild på ett "klaffskåp med blänkare", en elektromekanisk signalanordning som förekom i början av<br />

1900 talet i paradvåningar. Från trycknappar i de olika rummen kunde man kalla på<br />

serveringspersonalen eller hembiträdet. Ringklockan i klaffskåpet ringde och motsvarande "blänkare"<br />

för respektive rum föll ned. När uppdraget utförts tryckte betjäningen på knappen under "blänkaren"<br />

så att den återställdes. - Är det månne härifrån Microchip fått idén till sitt interrupt?<br />

14


Lösta programexempel med PIC16F628<br />

INT Interrupt, RB0/INT-pinnen<br />

External interrupt, yttre interrupt på pinnen RB0/INT genereras av en positiv eller<br />

negativ flank ( 0..1 / 1..0 ). Valet av flanktyp gör man med bit INTEDG i OPTIONregistret.<br />

Positiv flank gäller om INTEDG=1; och negativ flank om INTEDG=0;.<br />

Tänds lysdioden när man trycker på knappen så fungerar interruptet ...<br />

( Finns det någon som idag vet vad ”Killroy was here” betyder? )<br />

/* exint62x.c External interrupt turns on light */<br />

#include "16F628.h"<br />

#include "int16Cxx.h"<br />

#pragma config |= 0x3ff0<br />

#pragma origin 4<br />

interrupt int_server( void )<br />

{<br />

int_save_registers<br />

if( INTF == 1 ) /* test if it is the INT-interrupt? */<br />

{ /* this time it's obvius that it is! */<br />

PORTA.2 = 1; /* Lightdiode on to show "Killroy was here" */<br />

INTF = 0; /* Reset INT-flag before leaving */<br />

}<br />

int_restore_registers<br />

}<br />

void main( void )<br />

{<br />

/* the main program */<br />

CM0=1; CM1=1; CM2=1; /* no comparators at PORTA */<br />

TRISB.0 = 1; /* RB0/INT is input */<br />

TRISA.2 = 0; /* RA2 output to lightdiode */<br />

PORTA.2 = 0;<br />

INTEDG = 0; /* interrupt on negative going edge */<br />

INTE = 1; /* local enable */<br />

GIE = 1; /* global enable */<br />

while( 1 ) nop();<br />

}<br />

15


Lösta programexempel med PIC16F628<br />

RB-Interrupt. Interrupt on change RB7,<br />

RB6, RB5, RB4 -pinnarna<br />

Fyra av PORTB's pinnar har den speciella egenskapen att de kan generera interrupt så<br />

fort någon förändring inträffar. Denna funktion är främst tänkt för avkodning av<br />

tangentbord eller för avläsning av datormöss. Det passar också utmärkt för avkodning<br />

av pulsgivare ( se avläsning av pulsgivare ). Man bör inte göra något annat med<br />

PORTB om man valt denna funktion, därför har vi kopplat lysdioden till PORTA i<br />

stället.<br />

( Grundinställningen för PORTA's pinnar är som komparatoringångar. Detta kopplar<br />

man bort med kommandot: CM0=1; CM1=1; CM2=1; ).<br />

Inkoppling av pulsgivarkontakterna<br />

När man kopplar in kontakter till portpinnar behöver man i allmänhet sk. "pullup"-<br />

motstånd. En sluten kontakten kan ge "0"-nivån, men när kontakten är öppen blir<br />

nivån obestämd utan pullup. PIC-processorerna har inbyggda "pullup"-motstånd som<br />

kopplas in med kommandot<br />

OPTION.7 = 0; /* internal pullup resistors is on */.<br />

I figuren fins två 1k serie-resistorer som behövs för att "isolera" pulsgivaren från<br />

kretsprogrammeraren. Vid krets<strong>programmering</strong> används ju pinnarna RB6 och RB7<br />

( om vi inte hade haft behov av att omprogrammera PIC-kretsen hade dessa pinnar<br />

varit helt lediga och serieresistorerna onödiga ).<br />

En lysdiod på portpinne RA2 ska lysa varje gång pulsgivaren står i 0-läget.<br />

Programmet kan kontrolleras genom att man vrider pulsgivaren lika många steg<br />

framåt som bakåt och ger akt på att lysdioden tänds i 0-läget. Eftersom programmet<br />

utnyttjar "interrupt on change" är det inte troligt att processorn "tappar bort"<br />

räkneläget hur snabbt man än roterar pulsgivaren! Pröva!<br />

16


* rbint62x.c RB-int reads rotary switch */<br />

#include "16F628.h"<br />

#include "int16Cxx.h"<br />

#pragma config |= 0x3ff0<br />

int counter; /* global pulsecounter */<br />

char trans; /* global for transitions */<br />

#pragma bit lightdiode @ PORTA.2<br />

#pragma bit quad_A @ PORTB.6<br />

#pragma bit quad_B @ PORTB.7<br />

#define ON 1<br />

#define OFF 0<br />

#pragma origin 4<br />

interrupt int_server( void )<br />

{<br />

int_save_registers<br />

if( RBIF == 1 ) /* is it the RB pin change-interrupt? */<br />

{ /* this time it's obvius that it is! */<br />

/* read encoder new value */<br />

trans.0 = quad_A;<br />

trans.1 = quad_B;<br />

/* compare with old value */<br />

if( trans == 0b00.01 ) counter ++;<br />

if( trans == 0b01.00 ) counter --;<br />

/* replace old value with new value */<br />

trans.2 = trans.0;<br />

trans.3 = trans.1;<br />

RBIF = 0; /* Reset RB-change flag before leaving */<br />

}<br />

int_restore_registers<br />

}<br />

void main( void )<br />

{<br />

/* the main program */<br />

CM0=1; CM1=1; CM2=1; /* no comparators on PORTA */<br />

OPTION.7 = 0; /* internal pullup resistors on */<br />

TRISA.2 = 0; /* PA2 is output to lightdiode */<br />

PORTA.2 = 0;<br />

TRISB = 0b11000000; /* only inputs quad_A and quad_B */<br />

trans = 0;<br />

counter = 0;<br />

RBIE = 1; /* local enable */<br />

GIE = 1; /* global enable */<br />

}<br />

while( 1 )<br />

{<br />

if ( counter == 0 ) lightdiode = ON;<br />

else lightdiode = OFF;<br />

}<br />

17


Timers<br />

Timer0<br />

Timer0 är den timer som alla PIC-kretsar har. Det är en 8-bitarsräknare.<br />

Den kan programmeras att räkna pulser från T0CKI-pinnen, eller de interna<br />

klockpulserna f osc /4.<br />

Den kan förses med en prescaler så att den räknar långsammare.<br />

När Timer0 "räknar runt" sätts en flagga T0IF, den kan även programmeras att då<br />

orsaka programavbrott ( de enklaste PIC-kretsarna saknar avbrottsmöjligheten ).<br />

Register TMR0 kan läsas och skrivas från programmet. Timer0 programmeras med<br />

OPTION-registret.<br />

OPTION-registret<br />

18


Timer1<br />

Timer1 är en 16-bitarsräknare. Den kan antingen räkna pulser från T1CKI-pinnen,<br />

räkna de interna klockpulserna f osc /4, eller räkna klockpulser från en egen<br />

kristalloscillator T1OSC. Det sista alternativet gäller användning med lågfrekventa<br />

klock-kristaller 32,7680 kHz som ger hög noggrannhet med låg strömförbrukning.<br />

Den egna kristalloscillatorn kan synkroniseras med processorns klocka ( Synk ). Om<br />

den inte synkroniseras ( Asynk ), så kan den fortsätta att gå även om processorn<br />

försätts i lågeffektläge, s.k. SLEEP-mode.<br />

En programmerbar prescaler ger tillsammans med räknarens 16 bitar Timer1 ett<br />

omfattande arbetsområde. När Timer1 "räknar runt" sätts en flagga TMR1IF, den kan<br />

även programmeras att då orsaka programavbrott.<br />

Register TMR1H och TMR1L kan läsas eller skrivas, ett i taget, från programmet.<br />

Detta kräver speciell teknik. Om Timer1 används i samverkan med CCP-enheterna får<br />

man tillgång till extra register som underlättar läsning/skrivning.<br />

Timer1 programmeras med T1CON.<br />

Register T1CON Timer1 control register<br />

b7 b6 b5 b4 b3 b2 b1 b0<br />

- - T1CKPS1 T1CKPS0 T1OSCEN !T1SYNK TMR1CS TMR1ON<br />

Timer1 input ClocK Prescale Select, T1CKPS: T1CKPS1 T1CKPS1<br />

11 = 1/8, 10 = 1/4, 01 = 1/2, 00 = 1/1<br />

Timer1 OSCillator Enable, T1OSCEN: T1OSCEN<br />

1 = oscillator enable, 0 = oscillator shut-off<br />

Timer1 external clock input SYNChronisation, T1SYNC: !T1SYNC<br />

1 = no synchronisation, 0 = synchronisation<br />

TiMeR1 Clock Source select bit, TMR1CS: TMR1CS<br />

1 = External clock RC0/T1OSO/T1CKI, 0 = internal clock f osc /4<br />

TiMeR1 On bit, TMR1ON: TMR1ON<br />

1 = enables Timer1, 0 = stops Timer1<br />

19


Timer2<br />

Timer2 är 8-bitarsräknare som främst används tillsammans med CCP-enheterna för att<br />

generera PWM-signaler. Om Timer2 inte behövs till PWM-generering kan den<br />

användas som intern "klocka" som med jämna mellanrum sätter en flagga TMR2IF. I<br />

samband med detta kan den programmeras att orsaka programavbrott. ( Observera att<br />

Timer2:s utgång inte är ansluten till någon yttre pinne ).<br />

Timer2 räknar tills den når det värde som står i periodregistret PR2 då den 0-ställs<br />

och börjar om. Timer2 använder klockpulser från f osc /4, men eftersom den både har en<br />

Prescaler och en Postscaler blir inställningsområdet stort ( lika stort som för Timer1<br />

16-bitsräknaren ).<br />

Timer2 ställs in med T2CON-registret. TMR2 kan läsas och skrivas från programmet.<br />

Hela Timer2 kan stängas av om den inte används, så att man minskar<br />

strömförbrukningen.<br />

Register T2CON Timer2 control register<br />

b7 b6 b5 b4 b3 b2 b1 b0<br />

- TOUTPS3 TOUTPS2 TOUTPS1 TOUTPS0 TMR2ON T2CKPS1 T2CKPS0<br />

Timer2 OUTput Postscale Select bits, TOUTPS:<br />

TOUTPS3 TOUTPS2 TOUTPS1 TOUTPS0<br />

0000 1/1 0100 1/5 1000 1/9 1100 1/13<br />

0001 1/2 0101 1/6 1001 1/10 1101 1/14<br />

0010 1/3 0110 1/7 1010 1/11 1110 1/15<br />

0011 1/4 0111 1/8 1011 1/12 1111 1/16<br />

Timer2 ON bit, TMR2ON: TMR2ON<br />

1 = Timer2 on, 0 = Timer2 off<br />

Timer2 ClocK Prescale Select bits, T2CKPS: T2CKPS1 T2CKPS0<br />

00 = 1/1, 01 = 1/4, 1x = 1/16<br />

20


Hur läser/skriver man till 16-bit<br />

"free running" Timer1?<br />

Timer1 är en 16-bitarsräknare. Den måste läsas av som två 8-bitarstal, de 8 mest<br />

signifikanta bitarna TMR1H och de 8 minst signifikana bitarna TMR1L. Detta kan vara<br />

ett problem eftersom Timern kan "slå runt" mellan avläsningarna av 8-bits talen.<br />

Följande kod visar det säkra sättet<br />

( men eventuellt interrupt måste vara avstängt under avläsningen):<br />

long unsigned int time;<br />

unsigned char TEMPH;<br />

unsigned char TEMPL;<br />

TEMPH = TMR1H;<br />

TEMPL = TMR1L;<br />

if (TEMPH == TMR1H) /* Timer1 has not rolled over = good value */<br />

{<br />

time = TEMPH*256;<br />

time += TEMPL;<br />

}<br />

else /* Timer1 has rolled over - no new rollover for some time */<br />

/* read a new good value */<br />

{<br />

time = TMR1H*256;<br />

time += TMR1L;<br />

}<br />

Det kan också vara problematiskt att skriva till en 16-bitarsräknare eftersom det måste<br />

ske som två 8-bitarstal. Detta är det säkra sättet (Interrupt måste vara avstängt under<br />

skrivningen):<br />

TMR1L = 0; /* clear low byte = no rollover for some time */<br />

TMR1H = 12345/256; /* high byte of constant 12345 */<br />

TMR1L = 12345%256; /* low byte of constant 12345 */<br />

Observera att Timer1 ofta används tillsammans med en CCP-enhet. Vid Capture läses<br />

Timer1 av på ett säkert sätt av hårdvaran och resultatet hamnar i registerparet CCPRH<br />

och CCPRL.<br />

long unsigned int time;<br />

time = CCPR1H*256 + CCPR1L<br />

21


CCP-enheterna,<br />

Capture/Compare/(PWM)<br />

PIC-processorena kan vara utrustade med en eller flera CCP-enheter<br />

(Capture/Compare/PWM). Varje CCP innehåller ett 16-bitarsregister ( 2×8 bit,<br />

CCPRH och CCPRL ) som kan användas som ett 16-bitars Capture-register, ett 16-<br />

bitars Compare-register, eller som ett 10-bitars PWM Master/Slave DutyCycleregister.<br />

CCP-enheten innehåller också en 16-bitars komparator. Varje CCP-enhet har<br />

en portpinne, CCP-pinnen, för anslutning till omvärlden.<br />

Man ställer in CCP-enheternas arbetssätt med CCPCON-register. Eftersom CCPenheterna<br />

antingen samverkar med Timer1 eller Timer2 måste man även göra timerinställningar.<br />

CCP-pinnen måste ställas in som ingång eller utgång med aktuellt TRIS-register.<br />

PIC16F628 har en CCP-enhet CCP1 ( pinne 9, RB3/CCP1 TRISB.3 ). Register<br />

CCP1CON, CCPR1H och CCPR1L.<br />

Register CCP1CON<br />

b7 b6 b5 b4 b3 b2 b1 b0<br />

- - CCP1X CCP1Y CCP1M3 CCP1M2 CCP1M1 CCP1M0<br />

CCP1X CCP1Y PWM Minst signifikanta bitar<br />

22


CCP1M3 CCP1M2 CCP1M1 CCP1M0 CCP mode select bits<br />

M3 M2<br />

M3 M2<br />

mode-Beskrivning<br />

M1 M0<br />

M1 M0<br />

mode-Beskrivning<br />

0000 Reset av CCP-modulen 1000 Compare, 1-ställ CCP-pinne, CCPIF-flagga<br />

Capture, varje neg flank,<br />

0100<br />

CCPIF-flagga<br />

1001 Compare, 0-ställ CCP-pinne, CCPIF-flagga<br />

Capture, var pos flank, CCPIFflagga<br />

0101<br />

1010 Compare, CCPIF-flagga<br />

Capture, var 4:e pos flank,<br />

Compare, speciell händelse, CCPIF-flagga<br />

0110<br />

CCPIF-flagga<br />

1011<br />

CCP1: 0-ställ Timer1<br />

Capture, var 16:e pos flank,<br />

0111<br />

CCPIF-flagga<br />

11xx PWM-mode<br />

CCPIF-flaggan ( CCP1IF ) kan användas till att orsaka avbrott, interrupt. Observera<br />

att flaggorna måste 0-ställas av programmet, att de inte 0-ställs automatiskt när de<br />

läses!<br />

Capture mode<br />

Capture innebär att Timer1:s ögonblicksvärde 16-bitar kopieras över till de två 8-<br />

bitarsregistren CCPR1H och CCPR1L, när en förutbestämd "händelse" inträffar på<br />

CCP1 - pinnen (pinne 9). De två 8-bitarstalen kan sammansättas till en 16-<br />

bitarsvariabel:<br />

unsigned long time;<br />

time = CCPR1H*256 ;<br />

time += CCPR1L ;<br />

Vid Capture sätts CCP1IF -flaggan i PIR1 -registret. Det går att programmera så att<br />

avbrott, Interrupt, utlöses av detta.<br />

• Ställ in Timer1, internal/external, prescaler, synk/asynk, on/off.<br />

• CCP1-mode.<br />

• TRISB.3 = 1;<br />

23


Compare mode<br />

Compare innebär att ett 16-bitars tal i CCPR1 -registren kontinuerligt jämförs med<br />

Timer1:s räknevärde. När överenstämmelse sker blir CCP1-pinnen<br />

hög/låg/oförändrad beroende på inställningen i CCP1CON.<br />

Överenstämmelsen kan programmeras att dessutom utlösa avbrott, interrupt.<br />

Förutom påverkan på CCP-pinnen, finns det möjlighet att välja en så kallad "speciell<br />

händelse" (special event). För CCP1 innebär denna 0-ställning av Timer1.<br />

Ställ in Timer1, internal/external, prescaler, synk/asynk, on/off.<br />

• CCP1-mode.<br />

• TRISB.3 = 0;<br />

Timer1, valbar period<br />

CCP-enheten kan 0-ställa Timer1 med special event, vid det tal som står i<br />

registerparet CCPR. Timer1:s periodtid förkortas (men inget avbrott sker<br />

/* fosc = 4 MHz, Timer1Period = 0,001 s (1000 Hz) */<br />

T1CON = 0b00.00.0.1.0.1;<br />

/* --.presc=1/1.osc=shut off.no sync.internal clk.Enable timer */<br />

/* CCPR = (fosc/4) * Timer1Period = 1000000*0,001 = 1000 */<br />

/* CCPR = 1000 = 0x3E8 CCPR1H = 0x3, CCPR1L = 0xE8 */<br />

CCPR1H = 0x3;<br />

CCPR1L = 0xE8;<br />

CCP1CON = 0b00.00.1011; /* --. 00. special event */<br />

/* Timer1Period is now 1 ms */<br />

24


Så här skulle PIC-kretsarnas CCP-enheter kunna<br />

användas i en bilmotor … ( exempel med PIC16F87x )<br />

Elektronisk styrning av<br />

förbränningsmotorer<br />

De flesta bilmotorer ( och många andra förbränningsmotorer ) är i dag elektroniskt<br />

styrda. Detta har gjort underverk med bilarna, men samtidigt har verkstäderna förlorat<br />

kontrollen över justeringen av dessa bilar, även märkesverkstäder kan ha problem<br />

med att justera det enklaste tomgångsproblem.<br />

Intresset för att lära sig hur motorstyrningarna egentligen fungerar är därför stort. På<br />

IT-universitetets Mekatronik-inriktning har genom åren flera projekt handlat om detta.<br />

Några teknologer har efter projekten kunnat söka sig till motorindustrin för liknande<br />

arbetsupgifter.<br />

De mikrostyrkretsar ( PIC16F87x ) som används i Mekatronik-inriktningens projekt<br />

har sitt ursprung i kretsar för bilindustrin, och det kan därför vara intressant att se<br />

hur de ingående enheterna kan användas för att lösa motorstyrningsuppgifter.<br />

Behovet av tändförställning<br />

När bränsle/luftblandningen antänds i kompressionskammaren, börjar<br />

förbränningen vid tändstiftet och sprids därifrån vidare i<br />

bränsle/luftblandningen. Det tar en given tid för hela blandningen att brinna,<br />

expandera och därmed tvinga kolven ner i loppet. Det är därför man måste<br />

börja tändningsprocessen ( Z ) innan kolven når övre dödläge (ÖD). Det är<br />

detta som kallas för tändförställning.<br />

Om motorns varvtal ökar så får blandningen kortare tid på sig att brinna.<br />

Man behöver därför öka tändförställningen med ökat varvtal.<br />

Nu är det så att den hastighet med vilken bränsle/luftblandningen brinner<br />

varierar med hur full cylindern är vid kompressionen ( cylinderfyllning ).<br />

Vid litet gaspådrag och höga varvtal fylls cylindern mindre än vid stort<br />

"gaspådrag" och låga varvtal.<br />

Det behövs således även olika tändförställningar vid samma varvtal<br />

beroende på "gaspådrag" ( egentligen menar vi motorbelastning i stället för<br />

"gaspådrag" ).<br />

• Z = f ( varvtal, motorbelastning )<br />

25


Givare för vinkel, varvtal, motorbelastning<br />

Vinkelläge<br />

Fordonsindustrin har hittat ett mycket billigt sätt<br />

att mäta motoraxelns vinkelläge. En induktiv<br />

givare avger pulser när motorns startkuggkrans<br />

roterar, och en annan induktiv givare avger en<br />

puls per varv då ett "indexstift" passerar. (<br />

Tekniken beskrivs i boken Mätgivare ).<br />

Antag att startkransen har 130 kuggar. För att<br />

grovt hålla reda på vinkelläget "räknar" man i<br />

princip de kuggar som passerar med en modulo-<br />

130-räknare.<br />

Mikrostyrkretsen PIC6F87x har tre interna räknare ( Timer0, Timer1 och Timer2).<br />

Timer0 är lämplig till denna uppgift, det är en åttabitarsräknare, dvs en modulo-256-<br />

räknare som "slår runt" vid talet 255 och då börjar om med talet 0. Timer0 kan<br />

programmeras att räkna pulser som inkommer på pinne T0CKI. Kugg-givaren ansluts<br />

till denna pinne.<br />

Man kan få ett avbrott (interrupt) i programkörningen var gång Timer0 "slår runt".<br />

Om man vid avbrotten passar på att direkt "ladda" Timer0 med talet 126<br />

(256-130=126) blir räknecykeln förkortad till 130 steg, dvs vi får en<br />

modulo-130-räknare. Aktuell kugge kan man få reda på genom att läsa av Timer0 och<br />

dra ifrån talet 126.<br />

Antag att indexstiftet är placerat vid kugge nr "0". Mikrostyrkretsen PIC6F87x har en<br />

yttre avbrottsingång INT. Indexpinnens givare ansluts till denna. Vid start av motorn<br />

"laddar" man Timer0 med talet 126 när index-pinnen passerar sin givare och<br />

genererar avbrott. Från denna stund håller Timer0 "reda på" kuggarna.<br />

26


Varvtal<br />

Varvtalsmätningen går i princip till så att man mäter den tid det tar för kuggarna att<br />

passera Kugg-givaren. Rotationen sker inte med konstant varvtal utan "ryckigt",<br />

eftersom förbränningsmotorer är explosionsmotorer.<br />

PIC-processorerna kan vara utrustade med en eller flera så kallade CCP-enheter<br />

(CaptureComparePWM). För tidmätning låter man CCP-enheten vara inställd på<br />

Capture, och använder Timer1. Timer1 är en 16-bitsräknare (2×8 bitar), den<br />

programmeras att räkna i takt med processorns klocka. CCP-enhetens ingångspinne<br />

ansluts till Kugg-givaren och programmeras att reagera på positiva pulsflanker. Vid<br />

varje kugge kommer då automatiskt Timer1 att "läsas av" ( = capture) och resultatet<br />

hamnar i CCP-enhetens register CCPRH och CCPRL. De två CCPR-registren kan<br />

hanteras som ett 16-bitarstal. Vid "Capture" anropas avbrottsrutinen ( interrupt ) och i<br />

denna kan tidsvärdet i CCPR-registren lagras undan. I princip beräknas tiden mellan<br />

två kuggpassager som skillnaden mellan två tidsvärden, och varvtalet som det<br />

inverterade värdet av denna tid.<br />

Motorbelastning<br />

Tryckgivare, varmtråds-massflödesmätare, trottelpotentiometer.<br />

Motorns belastning kan mätas på många olika sätt. Den klassiska metoden för<br />

förgasarmotorer är att mäta undertrycket i förgasaren med en tryckgivare. En annan<br />

metod är att mäta luftflödet till motorn med en varmtråds-massflödesmätare. Ett<br />

ungefärligt mått på motorns belastning kan man få genom att med en trottelpotentiometer<br />

mäta vilket "gaspådrag" föraren ger.<br />

( Givarna beskrivs i boken Mätgivare ).<br />

27


Dessa givare har alla en analog spänning som utstorhet, och ansluts därför enklast till<br />

PIC-processorernas inbyggda AD-omvandlare. ( PIC16F87x har AD-omvandlaren ).<br />

Tändförställningstabell<br />

Sambandet mellan tändpunktens förställning och varvtalet<br />

tillsammans med belastningen är komplicerat och beskrivs bäst<br />

som en "karta" ( mappad tändning ).<br />

Rent experimentellt kan man placera fordonet på en så kallad<br />

"rullande landsväg" och systematiskt ändra varvtal och<br />

belastning och för varje kombination "pröva fram" den bästa<br />

tändförställningen. Mätvärdena kan man samla i en<br />

Tändförställningstabell.<br />

Med Matlab kan man visualisera tändförställningstabellen.<br />

3-D plot med Matlab. (Klipp ut texten och prova den i Matlabs kommandofönster).<br />

Observera att tabellens värden är "fejkade" och är endast avsedda som principexempel.<br />

ignadv = [ 30 40 40 40 20 15 15 15<br />

20 40 40 40 25 20 20 20<br />

40 60 60 50 30 20 20 15<br />

40 70 70 60 40 25 25 20<br />

45 80 85 80 50 40 30 25<br />

50 85 85 70 60 50 35 30<br />

60 90 90 80 75 60 40 35<br />

70 95 95 80 75 65 50 40 ];<br />

surf(ignadv);<br />

28


Tabellfunktion i PIC-processorn.<br />

Så här kan tabellen programmeras som en funktion i PIC-processorn. När funktionen<br />

anropas med tex. ignitionmap(0,0) (rad 0 kolumn 0) returnerar den talet 30.<br />

/* speed 3 bits 0...7 ; torque 3 bits 0...7 */<br />

char ignitionmap(char speed, char torque)<br />

{<br />

char index;<br />

index = speed*8 + torque; /* index for 64 table entries */<br />

skip(index)<br />

return 30;<br />

return 40;<br />

... /* the next 60 table entries */<br />

...<br />

return 50;<br />

return 40;<br />

}<br />

Tändningen<br />

Med hjälp av Timer0 vet man motorns vinkel. När man närmar sig tändpunkten mäter<br />

man tiden mellan passagen av två kuggar med den första CCP-enhetens Capturefunktion,<br />

och motorns belastning med hjälp av AD-omvandlaren. Genom att anropa<br />

tabellfunktionen så får man veta vilken tändförställning som är den rätta.<br />

För att utföra tändningen vid rätt tidpunkt låter man PIC-processorns andra CCPenhet<br />

vara inställd på Compare.<br />

När motorn nått den kugge som innehåller intervallet för tändningen, laddar man<br />

CCP-registren med ett tal, en tidsfördröjning. En programmerbar "händelse" utförs<br />

med CCP-pinnen då Timer1 når ( compare ) detta tal. Tändspolens drivkrets har<br />

anslutits till CCP-pinnen på ett sådant sätt så att den programmerade "händelsen"<br />

utlöser gnistan.<br />

CCP-enheten är så konstruerad att "händelsen" åtföljs av att Timer1 0-ställs ( och i fallet med CCPenhet<br />

2 att AD-omvandlaren startas ). Ett anrop till avbrottsrutinen ( interrupt ) kan ske nu om så<br />

önskas.<br />

29


Lösta programexempel med PIC16F628<br />

Indirekt frekvensmätning, periodtid,<br />

multiperiodtid<br />

Frekvensmätning sker ofta indirekt som periodtidsmätning ( T ), varefter frekvensen<br />

( f ) beräknas som f = 1/T. Periodtiden är tiden mellan två lika flanker hos<br />

mätsignalen. Frekvensmätning kan ske inom ett brett intervall eftersom Timer1 har en<br />

Prescaler och det dessutom är möjligt att programmera mätning av, varje eller var<br />

fjärde eller var åttonde flank (multiperiodmätning).<br />

Vi antar att f osc = 4 MHz, och att Timer1 räknar T1CK = f osc /4 = 1 MHz.<br />

Timer1-ticket blir då 1 µs och Timer1-perioden 2 16 ×1µs = 66 ms ( 15,3 Hz ).<br />

Om en 100 kHz-signal ( med periodtiden 10 µs ) inkommer på CCP-pinnen hinner det<br />

gå 10 Timer1-tick mellan två positiva flanker. Om det i stället är en 10 kHz-signal går<br />

det 100 Timer1-tick mellan två flanker, och vid en 1 kHz-signal blir det 1000<br />

Timer1-tick. Vid 100 Hz blir det 10000 Timer1-tick.<br />

Är frekvensen 10 Hz hinner Timer1 räkna mer än ett helt varv mellan två flanker.<br />

Vid höga frekvenser som tex en 100 kHz-signalen kan man öka mätupplösningen<br />

genom att bara registrera var åttonde flank ( det går då 80 Timer1-tick mellan de<br />

registrerade flankerna). Vid låga frekvenser som 10 Hz-signalen måste man antingen<br />

hålla räkning på hur många gånger Timer1 räknar runt, eller låta Timer1 räkna<br />

långsammare genom att använda prescalern, tex. 1/8.<br />

30


Programexempel frekvensmätning med 16F628<br />

/* fmeas62x.c frequency measurement for 16F628 */<br />

/* input frequency 100 Hz - 10 kHz at CCP1-pin */<br />

#include "16f628.h"<br />

#include "math16.h"<br />

#pragma config |= 0x3ff1<br />

/* function prototypes */<br />

void unslongtoa( unsigned long ); /* in ascnum.c */<br />

void initserial(); /* in seriF62x.c */<br />

bit putchar( char ); /* in seriF62x.c */<br />

void delay( char ); /* in delays.c */<br />

void string_out( void ); /* in stringIO.c */<br />

char text1( char ); /* function prototype */<br />

char s[11]; /* Global char array for digits */<br />

#include "seriF62x.c"<br />

#include "ascnum.c"<br />

#include "stringIO.c"<br />

#include "delays.c"<br />

void main(void)<br />

{<br />

char i;<br />

uns16 T, f, t1, t2;<br />

initserial();<br />

TRISB.3 = 1; /* CCP1-pin is input */<br />

/* Settings */<br />

/* "--"."Prescale 1/1"."osc shut off"."synchr"."fosc/4"."on" */<br />

T1CON = 0b00.00.0.0.0.1 ;<br />

/* "--"."--"."Capture each pos edge" */<br />

CCP1CON = 0b00.00.0101 ;<br />

while(1)<br />

{<br />

CCP1IF = 0 ; /* reset flag */<br />

while (CCP1IF == 0 ); /* wait for capture */<br />

t1 = CCPR1H*256;<br />

t1 += CCPR1L;<br />

CCP1IF = 0 ; /* reset flag */<br />

while (CCP1IF == 0 ); /* wait for next capture */<br />

t2 = CCPR1H*256;<br />

t2 += CCPR1L;<br />

/* Calculations */<br />

T = t2 - t1; /* calculate period */<br />

f = 1000000/T; /* calculate frequency */<br />

31


* print textstring text1 */<br />

for(i = 0; putchar( text1(i)); i++) ;<br />

/* print variable f */<br />

unslongtoa(f);<br />

string_out();<br />

putchar('\r'); putchar('\n');<br />

}<br />

}<br />

delay( 10 );<br />

/* String to write */<br />

char text1( char x)<br />

{<br />

skip(x);<br />

#pragma return[] = "The frequency is [Hz]: " '\0'<br />

}<br />

Hjälpfunktioner finns i filerna seriF62x.c, ascnum.c,<br />

stringIO.c, delays.c<br />

Programmet utnyttjar funktioner som ligger i andra filer och som därför måste tas<br />

med i arbetsbiblioteket.<br />

SeriF62x.c ( funktionsprototyp seriF62x.h )<br />

ascnum.c ( funktionsprototyp ascnum.h )<br />

stringIO.c ( funktionsprototyp stringIO.h )<br />

delays.c ( funktionsprototyp delays.h )<br />

Förmodligen har Du ingen tongenerator hemma. Den som vill pröva<br />

frekvesmätningsprogrammet kan koppla frekvensdelarkretsen 74HC4040’s<br />

klockingång till PIC-processorns RA6/CLKOUT (1MHz). På 4040-kretsens ben<br />

återfinner man 1 MHz neddelat steg för steg, dvs. frekvenserna 500 kHz, 250 kHz,<br />

125kHz, … 244Hz. Vid labtillfället i skolan finns en tongenerator och FLUKE<br />

scopemeter-123.<br />

32


PWM, Pulse Width Modulation<br />

Medan mikrostyrkretsar ofta har inbyggda AD-omvandlare, är det mer sällan som de<br />

också har DA-omvandlare. Anledningen till detta är att det finns en genväg att digitalt<br />

framställa analoga signaler - PWM, Pulse Width Modulation, pulsbreddsmodulering.<br />

Pulsbreddsmodulerad spänning, många komponenter märker bara det analoga medelvärdet.<br />

En pulsbreddsmodulerad signal består av en snabb följd av pulser. Många<br />

komponenter är "tröga" och hinner inte uppfatta att signalen ändrar sig, utan reagerar<br />

på signalens medelvärde som om det vore en konstant spänning (medelspänningen).<br />

Det gäller tex motorer, glödlampor och värmeelement. Har man snabba komponenter<br />

som skulle kunna upptäcka variationerna så filtrerar man PWM-signalen för att få ett<br />

konstant värde.<br />

PWM-signalen har fast frekvens och periodtid, medan pulstiden ( t 1 ) varieras på<br />

pulsluckans ( t 0 ) bekostnad. Signalens DutyCycle = t 1 / ( t 1 + t 0 ) är direkt<br />

proportionell mot det analoga medelvärdet.<br />

Hur programmerar man en PWM-signal?<br />

( ett avskräckande exempel utan PWM-enhet )<br />

I detta exempel låter vi lysdioden styras med en PWM-signal så att den lyser med<br />

75% intensitet ( medelvärde ) då strömställaren är "1" och med 25% intensitet då<br />

strömställaren är "0".<br />

33


Alla PIC-processorer har åtminstone en inbyggd räknare, Timer0. Timer0 är en 8-<br />

bitsräknare, den räknar således hela tiden från 0 till 255 då den slår runt och börjar<br />

från 0 igen. Med OPTION - registret väljer man räknehastigheten.<br />

Med ett tal Duty mellan 0 och 255 styr man PWM-signalen. Så länge Timer0 räknar<br />

under talet Duty låter man PWM-utgångspinnen vara "1", för att 0-ställa den så fort<br />

Timer0 räknat förbi talet Duty.<br />

Eftersom programmet hela tiden ( åtminstone varje Timer0-tick ) måste "läsa av"<br />

Timer0 får man inte så mycket processortid över för annat med denna metod ( ju<br />

högre PWM-frekvens, desto mindre tid till övriga uppgifter ).<br />

Många PIC-processorer har inbyggda "PWM-enheter". De kan självständigt generera<br />

PWM-signaler utan allt för stor inblandning från "huvudprogrammet".<br />

PWM-signal med Timer0<br />

/*<br />

pwmpic.c Timer0-PWM-signal ( Bad example! )<br />

===========================================<br />

*/<br />

#include "16F628.h"<br />

#pragma config |= 0x3ff0<br />

#define ON 1<br />

#define OFF 0<br />

#pragma bit PWM_out @ PORTB.0<br />

#pragma bit Contact @ PORTB.1<br />

char MoreToDo(void);<br />

char Duty; /* global variable */<br />

void main( void)<br />

{<br />

Duty = 0; /* start with zero DutyCycle */<br />

TRISB = 0b11111110 ; /* PORTB.0 is output */<br />

/* PORTB.1 is input */<br />

OPTION = 0b11000111; /* Timer0 Prescaler divide by 256 */<br />

while (1) /* forever */<br />

{<br />

char i;<br />

if (TMR0 < Duty) PWM_out = 1;<br />

else PWM_out = 0;<br />

Duty = MoreToDo(); /* Be back in one TIMER0-tick ! */<br />

}<br />

}<br />

34


* Function*/<br />

char MoreToDo(void)<br />

{<br />

char Duty;<br />

if( Contact == ON ) Duty = 191; /* 191/255 = 75% Dutycycle */<br />

/* _______ __ */<br />

/* _| |__| */<br />

/* */<br />

else Duty = 64; /* 64/255 = 25% Dutycycle */<br />

/* __ __ */<br />

/* _| |_______| */<br />

/* */<br />

return Duty; /* Be back in one TIMER0-tick! */<br />

}<br />

I exemplet ovan antar vi att PIC-processorn har klockfrekvensen 4 MHz och att<br />

Timer0 använder prescalerns /256 och därför räknar med frekvensen ( f osc /4 )/256 =<br />

10 6 /256 = 3906 Hz, tiden mellan två Timer0-tick blir 0,25 ms.<br />

PWM-signalens periodtid blir (1/3906)×256 = 66 ms. PWM-signalens frekvens blir<br />

därför låg, 15 Hz.<br />

Denna signal duger till ett värmeelement, medan en glödlampa riskerar att "flimra"<br />

störande.<br />

Om Timer0 inte använder prescalern blir PWM-frekvensen 256 ggr högre dvs 3900<br />

Hz, men då blir utrymmet för andra programaktiviteter än själva genereringen av<br />

PWM-signalen obefintliga.<br />

Ofta vill man ha PWM-frekvenser över 20000 Hz så att man undviker att vibrationer i<br />

utrustningen blir "hörbara" (för människor).<br />

Även om det går att programmera en PWM-signal direkt, så finns det således ett stort<br />

behov av att i stället kunna använda inbyggda "PWM-enheter" som kan avlasta<br />

processorn.<br />

( Microchip visar i sin application note AN654 på bättre sätt att programmera PWM<br />

med Timer0 genom att utnyttja avbrott då timern "slår runt" ).<br />

35


PWM med CCP-enheter<br />

( CaptureComparePWM )<br />

CCP-enheterna kan användas till att generera PWM-signaler. Detta sker i samverkan<br />

med Timer2.<br />

Timer2 är en 8-bitars räknare, vid PWM kompletteras den internt i processorn med 2<br />

bitar till en 10-bitarsräknare. Därigenom kan PWM-signaler med upp till 10 bitars<br />

upplösning genereras. PWM-signalens periodtid kan ställas in med Timer2:s prescaler<br />

( T2CON ) och med periodregistret PR2. Inställningen i periodregistret påverkar hur<br />

långt Timer2 räknar, och därigenom även PWM-signalens upplösning. Högst<br />

upplösning får man om Timer2 räknar fullt, dvs PR2 = 255, och periodtiden bara<br />

justeras med prescalern T2CON.<br />

(Timer2 som "10-bitarsräknare" räknar f osc inte f osc /4 ).<br />

Timer2 har även en postscaler, men den används inte vid PWM. Eftersom denna kan<br />

generera regelbundna avbrott kan man använda den till att uppdatera PWM-enhetens<br />

DutyTime.<br />

Från CCP-enheten används två 8 bitarsregister CCPRL och CCPRH och en 16-bitars<br />

komparator ( bara 10 bitar används ). CCPRH programmeras inte av användaren utan<br />

är bara en intern buffert vid PWM.<br />

PWM-signalens DutyTime, T Duty , "beställs" som 8 mest signifikanta bitar med<br />

registret CCPRL ( trots registrets namn! ) och 2 extra minst signifikanta bitar från<br />

registret CCPCON bitarna CCPX och CCPY.<br />

Om man har 10 bitars upplösning, men nöjer sig med 8 bitars upplösning, räcker det<br />

att placera 8 bitar för önskad DutyTime, T Duty , i registret CCPRL, och man behöver i<br />

så fall inte bry sig om de två extra bitarna i register CCPCON utan låter dem vara 0.<br />

36


Funktion<br />

• Timer2 räknar tills den når talet i periodregistret PR2, då 0-ställs timern och<br />

samtidigt 1-ställs en SR-låskrets. Detta påbörjar PWM-pulsen på CCP-pinnen.<br />

• Nu laddas de 10 bitarna "DutyTime" från "CCPRL×4 + CCPX×2+ CCPY" till<br />

komparatorn.<br />

• 10-bitars komparatorn upptäcker när Timer2 räknat fram till talet för<br />

DutyTime.<br />

• Då 0-ställs SR-låskretsen och PWM-pulsen avslutas på CCP-pinnen.<br />

• När Timer2 räknat fram till periodvärdet 0-ställs timern på nytt och förloppet<br />

upprepas - allt detta utan medverkan från processorn.<br />

PWM-frekvens, upplösning och DutyCycle<br />

För upplösningen gäller:<br />

För DutyCycle gäller:<br />

Exempel (med PIC16F628)<br />

Antag att vi har en PIC16F628 med 10 MHz klocka. Högst upplösning får man om<br />

Timer2 räknar fullt, dvs PR2 = 255 och prescalern delar med 1/1 ( ställs in med<br />

T2CON ). Prescalern kan ställas in på: 1:1, 1:4, 1:16 {1 4 16}.<br />

f PWM = 10⋅10 6 / (256×4×1) = 9766 Hz<br />

Upplösning = log(10000000/9766) / log(2) = 3/0,3 = 10 bitar<br />

Om denna PWM-frekvens kan accepteras och om man nöjer sig med 8 bitars<br />

upplösning blir <strong>programmering</strong>en av DutyCycle enkel:<br />

37


* Settings CCP1 PWM-mode */<br />

TRISB.3 = 0; /* CCP1 is output (RB3 pin) */<br />

PR2 = 255; /* Timer2 counts full period */<br />

/* PWM DutyCycle */<br />

CCPR1L = 64; /* in this example 25% DutyCycle 65/256 = 0,25 */<br />

/* CCP1X and CCP1Y can be set to 0 for 8 bit resolution */<br />

/* 0, 0000 (1:1 postscale), 1 (TMR2 on), 00 (1:1 prescale) */<br />

T2CON = 0b00000100; /* Set CCP unit in PWM-mode */<br />

CCP1CON = 0b00.0.0.1111;<br />

/* --, 0 (CCP1X), 0 (CCP1Y), 1111 (PWMmode 11xx) */<br />

Vill man använda en högre PWM-frekvens, tex 20 kHz för att undvika hörbara<br />

vibrationer, laddar man PR2 med talet 124. Högsta upplösningen blir då 9 bitar.<br />

Löst programexempel med PIC16F628<br />

PWM-testsignal, 1000 Hz 50%<br />

dutycycle<br />

Detta mycket enkla testprogram visar hur PIC16F62x ställs in för att för att generera<br />

en 1000 Hz, 50% dutycycle PWM-signal. Hemma kan testsignalerna avlyssnas med<br />

kristall-hörluren och kanske jämföras med ett stämt musikinstrument, eller telefonens<br />

kopplingston som är 440 Hz..<br />

( Vid labtillfället i skolan mäter vi frekvensen med en FLUKE 123-scopmeter. )<br />

38


PR2-registret<br />

Om oscillatorfrekvensen är 4 MHz och Timer2's prescaler är inställd på 1/4<br />

( T2CON ) så ska periodregistret PR2 således laddas med talet 249.<br />

CCPR 10 bitar<br />

50% Duty-cycle ger:<br />

Enklaste sättet att variera Duty-cycle är nu att skriva tal mellan 0 och 249 till CCPRLregistret.<br />

( Skriver man ett tal i CCPRL-registret som är större än talet i PR2-registret, tex 250,<br />

så upphör PWM-signalen).<br />

Programmet pwm1k62x.c<br />

/* pwm1k62x.c PWM testprogram for 16f628 */<br />

/* PWM frequency = 1000 Hz. PWM DutyCycle = 50%. */<br />

#include "16f628.h"<br />

#pragma config |= 0x3ff0<br />

void main(void)<br />

{<br />

/* Settings CCP1 PWM-mode */<br />

PR2 = 249;<br />

/* 10 bit Duty 0b01111101.0.0 = 500 */<br />

CCPR1L = 0b01111101;<br />

/* CCP1X = 0 CCP1Y = 0 later on */<br />

TRISB.3 = 0; /* CCP1 is output at RB3 ( pin 9 ) */<br />

T2CON = 0b0.0000.1.01;<br />

/* 0, 0000 (1:1 postscale), 1 (TMR2 on), 01 (1:4 prescale) */<br />

CCP1CON = 0b00.0.0.1111;<br />

/* --, 0 (CCP1X), 0 (CCP1Y), 1111 (PWMmode 11xx) */<br />

while(1) nop();<br />

}<br />

39


Lösta programexempel med PIC16F628<br />

Varvtalsratt till<br />

LEGO-motor<br />

Pulsgivare och PWM<br />

En ratt med pulsgivare är ansluten till RB6 och RB7. I skolan ansluter vi en Legomotor<br />

mellan RB3/CCP1 (PWM-signalen) och RA1. ( Hemma får Du nöja dig med<br />

två antiparallella lysdioder vars ljusstyrka symboliserar motorns varvtal ).<br />

Pulsgivaren ska användas som varvtalsratt för legomotorn (öka/minska). Full fart<br />

uppnås när man vridit fram pulsgivaren 255 steg.<br />

Programmet måste ignorera om man vrider fram mer än 255 steg eller bakåt förbi ”0”.<br />

/* pwmspeed.c RB-int reads rotary switch and outputs pwm */<br />

#include "16F628.h"<br />

#include "int16Cxx.h"<br />

#pragma config |= 0x3ff0<br />

char count; /* global to accumulate pulses from sensor */<br />

char trans; /* global to store transitions */<br />

#pragma bit lightdiode @ PORTA.2<br />

#pragma bit quad_A @ PORTB.6<br />

#pragma bit quad_B @ PORTB.7<br />

#define ON 1<br />

#define OFF 0<br />

40


#pragma origin 4<br />

interrupt int_server( void )<br />

{<br />

int_save_registers<br />

if( RBIF == 1 ) /* is it the RB pin change-interrupt? */<br />

{ /* this time it's obvius that it is! */<br />

/* read encoder new value */<br />

trans.0 = quad_A;<br />

trans.1 = quad_B;<br />

/* compare with old value */<br />

if( (trans == 0b00.01) && (count != 255) ) count ++;<br />

if( (trans == 0b01.00) && (count != 0) ) count --;<br />

/* replace old value with new value */<br />

trans.2 = trans.0;<br />

trans.3 = trans.1;<br />

RBIF = 0; /* Reset RB-change flag before leaving */<br />

}<br />

int_restore_registers<br />

}<br />

void main( void )<br />

{<br />

/* the main program */<br />

char speed;<br />

CM0=1; CM1=1; CM2=1; /* no comparators on PORTA */<br />

OPTION.7 = 0; /* internal pullup resistors on */<br />

TRISA.1 = 0; /* PA1 is output. Motor GND. */<br />

PORTA.1 = 0; /* Motor GND = 0 */<br />

TRISB = 0b11000000; /* only inputs quad_A and quad_B */<br />

trans = 0;<br />

speed = 0;<br />

RBIE = 1; /* local enable */<br />

GIE = 1; /* global enable */<br />

/* Settings CCP1 PWM-mode */<br />

PR2 = 255;<br />

TRISB.3 = 0; /* CCP1 is output (RB3-pin) */<br />

T2CON = 0b0.0000.1.00;<br />

/* 0, 0000 (1:1 postscale), 1 (TMR2 on), 00 (1:1 prescale) */<br />

CCP1CON = 0b00.0.0.1111;<br />

/* --, 0 (CCP1X), 0 (CCP1Y), 1111 (PWMmode 11xx) */<br />

while(1)<br />

{<br />

speed = count;<br />

CCPR1L = speed; /* new PWM-value */<br />

}<br />

}<br />

41


Programuppgift med PIC16F628<br />

Varvtalsratt till<br />

LEGO-motorn,<br />

medurs/moturs<br />

rotation<br />

Pulsgivare och PWM<br />

Uppgiften är nu att med pulsgivarens ratt styra motorns varvtal i två rotationsriktningar.<br />

När ratten vrids åt ena hållet ska hastigheten öka, om den vrids tillbaka åt andra hållet<br />

ska den minska tills den blir ”0”. Fortsätter man nu vridningen ska motorn byta<br />

rotationsriktning och ratten ska styra hastigheten i den andra riktningen!<br />

Byte av rotationsriktning<br />

Byte av rotationsriktning kan ske genom att man ändrar spänningen vid motorns<br />

jordpunkt från ”0” till ”1”. Om jordspänningen är ”0” ger PWM-signalen en proportionell<br />

hastighet i ena rotationsriktningen. Om jordspänningen är ”1” ger PWMsignalen<br />

en omvänt proportionellt varvtal i den motsatta rotationsriktningen, här bör<br />

man således programmera DutyCycle’s inverterade värde ( se figur )!<br />

Detta låter komplicerat, men det är faktiskt enklare än det ser ut eftersom det stämmer<br />

överens med hur datorer lagrar tal med tecken.<br />

Om variabeln count inte är teckenlös (char) utan i stället deklareras som ett tal<br />

med tecken ( int count; ) har den talområdet –128 < 0 < +127. Max- och mingränserna<br />

i programmet måste då omdefinieras:<br />

if( (trans == 0b00.01) && (count != 127) ) count ++;<br />

if( (trans == 0b01.00) && (count != -128) ) count --;<br />

42


Om man studerar de binära talen -128 (10000000), -1 (11111111), 0 (00000000),<br />

+1 (00000001), +127 (01111111), ser man att den mest signifikanta biten count.7<br />

är teckenbiten och att denna kan användas för att styra motorns rotationsriktning.<br />

PORTA.1 = count.7; /* rotate cw or ccw */<br />

Talcirkel. Registeraritmetik innebär att talsystemet kan ses som en "ring" - som km-räknaren på en bil.<br />

För enkelhets skull är registerlängden i figuren fyra bitar, då räknar registret "runt" efter talet 15.<br />

Principen är densamma för ett 8-bitars register, men det räknar runt efter talet 255.<br />

Tal kan antingen betraktas som teckenlösa tal ( tex typ char i C ) eller som tal med tecken<br />

( typ int i C ).<br />

Om man fördubblar talet count får man ett lämpligt värde att styra PWM-enheten<br />

med, detta visar nedanstående tabell:<br />

count count.7 count*2 count count.7 count*2<br />

+127 0 +254 -128 1 0<br />

(01111111)<br />

(11111110) (10000000)<br />

(00000000)<br />

0<br />

0 0<br />

-1 1 +254<br />

(00000000)<br />

(00000000) (11111111)<br />

(11111110)<br />

char speed;<br />

speed = count*2;<br />

CCPR1L = speed; /* new PWM-value */<br />

Det hela passar som ”hand i handske”!<br />

Inför dessa ändringar i programmet pwmspeed.c . Provkör<br />

därefter programmet och styr motorn med olika varvtal och<br />

rotationsriktningar.<br />

43


Lösta programexempel med PIC16F628<br />

TickClock, samplingsklocka<br />

I mekatroniken styr man processer med hjälp av ställdon utifrån mätvärden från<br />

givare. En mikrostyrkrets beräknar lämpliga åtgärder som sätts in vid regelbundna<br />

tidpunkter, samplingstillfällen.<br />

Det är verkligen viktigt att samplingsfrekvensen är konstant, eftersom storleken på en<br />

åtgärd naturligtvis måste bero på hur ofta som den utförs.<br />

En mycket vanlig <strong>programmering</strong>suppgift är därför att generera regelbundna<br />

programavbrott, med en sk samplingsklocka. Man kan se detta som det mekatroniska<br />

systemets hjärtslag!<br />

PIC-processorerna är förberedda för att enkelt kunna utföra detta.<br />

TIMER2 som samplingsklocka<br />

TIMER2:s periodtid är mycket enkel att påverka med ett 8-bitars periodregister PR2.<br />

Den har dessutom både en postscaler ( 1/1 1/4 1/16 ) och en prescaler ( 1/1 ... 1/16 ).<br />

När postscalern "slår runt" sätts en flagga TMR2IF och då kan även interruprutinen<br />

köras om man programmerat detta.<br />

Ofta används TIMER2 för generering av PWM-signaler. Postscalern ingår inte för<br />

genereringen av PWM-frekvensen, men den kan ändå användas som<br />

samplingsklocka.<br />

44


Antag att vi har 4 MHz kristall och att vi behöver en 750 Hz samplingsklocka (tex för<br />

att uppdatera PWM-signalen). TIMER2:s prescaler är 1/1 och periodregistret<br />

PR2=255. ( Jämför med exemplet Varvtalsratt till LEGO-motorn ). PWMfrekvensen<br />

blir:<br />

10 6 /256 = 3906 Hz. För att få en 750 Hz samplingsklocka måste vi dela<br />

pwmfrekvensen med 5 med postscalern. 3906/5 = 750 Hz.<br />

Programmet kan provas genom att man kontrollerar att har en ton med frekvensen<br />

750/2 Hz på pinne RB0 och att man har en ton med frekvensen 3906 Hz från CCP1<br />

pinnen RB3.<br />

/* pwmclk.c sampleclock 750 Hz with TIMER2 and pwm 3000 Hz */<br />

#include "16F628.h"<br />

#include "int16Cxx.h"<br />

#pragma config |= 0x3ff0<br />

#pragma origin 4<br />

interrupt int_server( void )<br />

{<br />

int_save_registers<br />

if( TMR2IF == 1 ) /* test if it is the TMR2I-interrupt? */<br />

{ /* this time it's obvius that it is! */<br />

PORTB.0 = !PORTB.0; /* togle earphone, 750/2 Hz tone at RB0 */<br />

TMR2IF = 0; /* Reset TMR2IF-flag before leaving */<br />

}<br />

int_restore_registers<br />

}<br />

void main( void )<br />

{<br />

/* the main program */<br />

TRISB.0 = 0; /* RB0 output to earphone */<br />

PORTB.0 = 0;<br />

/* Settings CCP1 PWM-mode */<br />

PR2 = 255;<br />

/* 10 bit startup Duty 0b01111101.0.0 */<br />

CCPR1L = 127; /* startup value */<br />

/* CCP1X = 0 and CCP1Y = 0 later on in CCP1CON */<br />

TRISB.3 = 0; /* CCP1 is output (RB3-pin) 3000 Hz tone */<br />

T2CON = 0b0.0100.1.00;<br />

/* 0, 0100 (1:5 postscale), 1 (TMR2 on), 00 (1:1 prescale) */<br />

CCP1CON = 0b00.0.0.1111;<br />

/* --, 0 (CCP1X), 0 (CCP1Y), 1111 (PWMmode 11xx) */<br />

TMR2IE = 1; /* local enable */<br />

PEIE = 1; /* regional enable */<br />

GIE = 1; /* global enable */<br />

while( 1 ) nop(); /* main program has nothing to do ! */<br />

}<br />

45


PIC16F87x, AD-omvandlarens<br />

referensspänning<br />

Blockschema över PIC-midrangekretsarnas AD-enhet.<br />

Matningsspänningen som spänningsreferens<br />

För att kunna mäta analoga spänningar har PIC-midrangekretsarna en 10-bitars ADomvandlare.<br />

Med hjälp av en inbyggd sk. multiplexer har man möjlighet att ansluta<br />

omvandlaren till en i taget av 8 portpinnar. Som referens vid AD-omvandlingen<br />

används antingen kretsens matningsspänning, (V SS , V DD ), eller så kan man ansluta<br />

yttre spänningsreferenser genom två av portpinnarna. Det enklaste är naturligtvis att<br />

använda matningsspänningen som referens, men det kan finnas olika anledningar till<br />

att använda yttre spänningsreferens.<br />

Yttre precisionsreferens<br />

Antag att PIC-processorn ska användas som en noggrann voltmeter. Det är då viktigt<br />

att den uppmätta spänningen jämförs med en exakt referens. Om utrustningen<br />

tillexempel är batteridriven, så varierar matningspänningen för mycket för att vara<br />

lämplig som referensspänning.<br />

46


PIC-processor som Voltmeter.<br />

Man kan ansluta en yttre referensspänning till ingången AN3. Det finns utmärkta<br />

spänningsreferenskretsar att köpa, tex kretsen MAX6241, som har spänningen 4,096<br />

V. Detta "ovanliga" spänningsvärde kommer av att talet 4096 är en tvåpotens, 2 12 =<br />

4096. Med den referensspänningen får AD-omvandlaren skalfaktorn 4 mV/steg vilket<br />

innebär att mätvärdet kan presenteras direkt, utan att behöva skalas om med<br />

multiplikation/division. Division med "4" kan man utföra genom att "skifta" det<br />

binära talet två steg åt höger.<br />

(Multiplikationer och divisioner innebär mycket programkod för processorer som<br />

saknar inbyggd multiplikatorenhet).<br />

Högsta upplösning<br />

Vill man ha högsta upplösning vid mätning av givarsignaler måste man se till att<br />

givarens utspänningsområde och det spänningsområde AD-omvandlaren omvandlar<br />

helt "täcker" varandra. I allmänhet krävs det då att både AD-omvandlarens<br />

referensspänning och 0-nivå kan justeras.<br />

Hög upplösning kräver att AD-omvandlarens referensspänning och 0-nivå kan justeras efter givarens<br />

spänningsområde.<br />

Med de två potentiometrarna i figuren kan AD-omvandlarens referensspänning<br />

(V REF+ ) ställas in mellan 2,5-5V och 0-nivå (V REF- ) mellan 0-2,5V. Genom att<br />

potentiometrarna är seriekopplade riskerar man inte att av misstag kunna ställa in en<br />

referensnivå som är lägre än 0-nivån.<br />

47


Anslutning av en termistor<br />

Ett praktiskt exempel är temperaturmätning med en NTC-termistor. En termistor har<br />

hög känslighet, men olinjärt temperatursamband.<br />

I praktiken seriekopplar man termistorn med en fast resistor så att den ingår i en<br />

spänningsdelare. ( Tekniken beskrivs i boken Mätgivare ). Figuren visar sambandet<br />

mellan spänning och temperatur för en sådan spänningsdelare. Kurvan börjar vid 0,5<br />

V och slutar vid 3,5V och blir däremellan förvånadsvärt linjär.<br />

Genom att ställa in V REF- = 0,5V och V REF+ = 3,5V med potentiometrarna, så utnyttjar<br />

temperaturgivaren AD-omvandlarens hela spänningsområde.<br />

48


Ratiometrisk anslutning<br />

Ratiometrisk anslutning av potentiometergivare till en AD-omvandlare.<br />

Som referensspänning kan den ostabiliserade matningsspänningen användas!<br />

Ibland behöver man stabila referensspänningar både till givare och AD-omvandlare.<br />

Ett ofta använt konstgrepp är då ratiometrisk anslutning av givaren. Så länge<br />

givarens referensspänning och AD-omvandlarens referensspänning ändrar sig i<br />

samma proportioner så omvandlas samma värde.<br />

Om man har samma referensspänning till givare och AD-omvandlare så ställs det<br />

därför inga krav på att den ska vara stabil.<br />

AD-omvandlare förses av detta skäl ofta med separata anslutningar för<br />

matningspänning och jord till sina analoga respektive digitala delar.<br />

( V DD V SS V REF+ V REF- ).<br />

Det viktiga är att båda referensspänningarna ansluts till samma punkter så att de<br />

utsätts för samma "störningar".<br />

PIC-processorn har separat anslutning för AD-omvandlarens referensspänning genom<br />

pinnarna AN2 och AN3.<br />

( Tekniken beskrivs i boken Mätgivare ).<br />

49


PIC16F87x AD-omvandling, steg för<br />

steg (utan användning av interrupt)<br />

1. Konfigurera AD-modulen<br />

Konfigurera analoga ingångar och referensspänningar (eller digitital<br />

I/O). Register ADCON1.<br />

• Välj AD-ingångskanal. Register ADCON0.<br />

• Bestäm AD-omvandlarens klockfrekvens. Register ADCON0.<br />

• Sätt på AD-modulen! Register ADCON0.<br />

• Välj format på hur 10-bitstalet ska justeras. Register ADCON1.<br />

Exempel:<br />

ADCON0=0bxxxxxxxx; ADCON1=0bxxxxxxxx; TRISA=0bxxxxxxxx;<br />

TRISE=0bxxxxxxxx;<br />

2. Vänta inställningstiden ( acquistion time ).<br />

• Acquistion time t ACQ är av storleksordningen 20 µs. Exempel på hur man<br />

programmerar 20 µs ungefärlig fördröjningstid (tids-spill) vid 4 MHz<br />

klockfrekvens:<br />

char i = 3; do ; while ( --i > 0);<br />

3. Starta AD-omvandlingen.<br />

• Ettställ GO/DONE biten. Register ADCON0. Kodexempel:<br />

GO = 1;<br />

4. Vänta tills AD-omvandlingen är klar.<br />

• Vänta ut att biten GO/DONE blir 0. Exempel på programkod:<br />

while( GO );<br />

5. Läs av AD-omvandlaren.<br />

• 8 bitar, ADRES vänsterjusterat. Kodexempel:<br />

char answer;<br />

answer=ADRESH;<br />

• 10 bitar, ADRES högerjusterat. Kodexempel:<br />

unsigned long answer;<br />

answer = 256*ADRESH; answer += ADRESL;<br />

( ADRES finns fördefinierad i headerfilen 16f874.h som ADRESH och ADRESL,<br />

GO/DONE finns fördefinierad i headerfilen 16f874.h som GO )<br />

50


PIC16F874<br />

Blinkfrekvensen styrs av potentiometern<br />

/* ADtest.c testprogram for 16f874 */<br />

/* Blinkfrequency at RD0 varies with voltage on AN0 */<br />

#include "16f874.h"<br />

#pragma config |= 0x3ff2<br />

#pragma bit out @ PORTD.0<br />

void main(void)<br />

{<br />

char i, j, answer, blinktime;<br />

TRISD.0 = 0; /* lightdiode at RD0 is output */<br />

out = 1;<br />

/* AD Setup. Se help Script! */<br />

/* "fosc/8"."AN0"."go=0"."-"."on" */<br />

ADCON0 = 0b01.000.0.0.1;<br />

/* "leftjust"."fosc/8"."--"."internal ref, Vref+=Vcc, Vref-=Vss" */<br />

ADCON1 = 0b0.1.00.1110;<br />

TRISA.0 = 1; /* AN0 is input */<br />

while(1)<br />

{<br />

char i = 3; do ; while ( --i 0); /* wait tACQ */<br />

GO=1; /* start AD */<br />

while(GO); /* wait for done */<br />

blinktime = ADRESH; /* read result 8 msb */<br />

/* double loop i,j generates blinkfrequency */<br />

for( i=0;i < blinktime; i++)<br />

{<br />

for( j=0;j < 250; j++);<br />

}<br />

/* toggle lightdiode on/off */<br />

out = ! out;<br />

}<br />

}<br />

51


JSP-diagram för AD-testprogram. Med en potentiometer till AD-omvandlaren kan man styra en<br />

lysdiods blinkfrekvens.<br />

52

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!