En projektrapport på kursen Mikrodatorteknik, 5 p, ht 2002, vid Fysikum, Lunds universitet. Location: http://www.df.lth.se.orbin.se/~mikaelb/micro/mbproj.html Datum: 27/12 2002 |
I denna rapport beskrivs ett bygg- och programmeringsprojekt för enchipsdatorn/mikrokontrollern PIC16C74A, en 8-bitars mikroprocessor från företaget Microchip. Tack vare att PIC:arna (Peripheral Interface Controller) är RISC-datorer (Reduced Instruction Set Computer) med Harward-arkitektur är de snabba trots att klockfrekvensen i detta projekt endast är 4 MHz. Jag skrev programmet i assemblerspråket MPASM och använde MPLAB IDE (Integrated Development Environment) som utvecklingsmiljö. Det finns även utvecklingsmiljöer för programspråket C. En använd PIC16C74A måste raderas med UV-ljus i 20 minuter innan man kan bränna ner programmet med en EPROM-programmerare som t.ex. PICSTART Plus. Det finns PIC:ar med FLASH-minne som kan omprogrammeras direkt. MPLAB och datablad kan hämtas gratis från http://www.microchip.com/. Eftersom PIC:arna är så populära så finns det många intressanta byggprojekt att hämta på Internet; t.ex. har jag hittat implementationer av I2C, USB, IrDA och andra intressanta protokoll. Följande projekt är dock helt egenutvecklat.
Problemet är lite konstgjort och är mest uttänkt för att vara lärorikt. Ingen har egentligen något behov av exakt denna produkt men kanske av något liknande. När jag konstruerade problemet så utgick jag ifrån vilka komponenter jag hade och vad som kunde bli en imponerande demonstration. Tänk dig en vanlig CPU-fläkt som styrs av en fjärrkontroll för en stereo. Man kan öka och minska varvtalet och dessutom visas antalet varv per sekund binärt med lysdioder.
Ursprungligen var det även tänkt att varvtalet skulle regleras exakt, men p.g.a. problem med störningar orsakade av metoden med vilken fläktens varvtal styrs (PWM) så har denna extrauppgift lagts ner. Jag har förövrigt provat alla sätt som föreslagits mig att minska störningarna, men inget fungerar speciellt bra. Som en liten ledtråd om vad man skulle kunna använda regleringen till nämner jag att fläkten har sju blad och låter man den snurra med 63 varv/sekund blir detta 441 Hz nära ettstrukna A. Man skulle kunna lysa med en laserstråle genom fläkten och få denna hackad med denna och andra bestämda frekvenser: användbart för optiska experiment eller musiktillämpningar.
Man skulle kunna koppla in en termistor på en analog ingång och på så sätt få en fläkt som även är temperaturstyrd. Vidare skulle man kunna lägga till en serieport (inbyggd i de flesta PIC:ar) och på så sätt få en fläkt som styrs av en persondator och som även skulle kunna skicka tillbaka information om varvtal och temperatur till persondatorn. Ett alternativ till att styra och övervaka med persondator vore att i stället göra detta med en grafritande miniräknare, t.ex. TI-82, TI-83 eller TI-89. Detta skulle kräva ett man implementerar miniräknarens kommunikationsprotokoll.
I och med att man kopplar spänning till kretsen, så startar fläkten på 90 % av maximalt varvtal och en lysdiod (B7) blinkar med en period på 1 s och en duty-cycle på 50 % vilken visar att kretsen är i läge on. Varvtalet visas binärt på lysdioderna B0 B7. Lysdiod B7 har alltså dubbla roller, men eftersom man oftast kör fläkten i mindre än 128 varv/s (7680 varv/min) så gör inte detta så mycket. Med Power-knappen på fjärrkontrollen kan man slå av och på fläkten. Läge off visas genom att lysdioden (B7) blinkar med en duty-cycle på 10 %. Med nuvarande version av programmet så har fläkten sju olika varvtal som väljs genom att trycka på Volume+ och Volume- på fjärrkontrollen. Varvtalen är numrerade från 0 till 6 och är på 0 %, 75 %, 80 %, 85 %, 90 %, 95 % och 100 % av maximal effekt. Varvtalet vid power-on har alltså nummer 4. Anledningen till valet av effekter (PWM duty-cycles) är att störningarna från fläkten är mindre vid höga varvtal.
En speciell finess är att lysdioderna blinkar till kort när ett kommando från fjärrkontrollen har mottagits: B6=Power, B5=Volume+, B4=Volume-, B3=Upprepning (d.v.s. en knapp har hållits nedtryckt länge används inte), B2=Okänt kommando (d.v.s. någon annan knapp än de som används i detta system har tryckts ned), B1=Okänd fjärrkontroll (händer oftast vid störningar).
Här beskriver jag vilka krav som ställs på komponenterna i detta projekt och vilka signaler de avger och tar emot.
CPU-fläkten som man kan använda i detta projekt är en standardfläkt för kylning av processorn i en persondator. Fläkten bör ha 3 sladdar: jord (svart), spänning (röd) och varvtal (gul). Utgången för varvtal är av open collector-typ, och alltså måste man koppla ett pull-up-motstånd på ca 11 kW från 5 V för att få en normal logisk utgång. Varvtalsutgången ger ut en fyrkantsvåg med en frekvens som är dubbla varvtalet, alltså 2 pulser för varje varv. Jag vet inte närmare vilken teknik som används för att ursprungligen generera pulserna, men varvpulserna kan genereras utan spänningstillförsel genom att snabbt rotera fläkten vilket enklast ordnas genom att blåsa kraftigt genom fläkten. Alltför låga varvtal ger inte upphov till pulser.
Fläkten jag råkade använda var en Evercool EC6025H12C som medföljde en AMD Duron 800 MHz CPU. Den blev över då jag bytte ut den mot en tystare temperaturstyrd fläkt. Evercoolen har egenskaperna 12 V, 0.2 A, 2.4 W.
Jag gjorde en mätning över antalet varv per sekund som funktion av tillförd effekt till fläkten enligt PWM-styrningen. P.g.a. störningarna kunde jag inte mäta varvtalet säkert med PIC:en utan fick använda ett gammalt oscilloskop. PIC:ens mätvärden bör dock betraktas som noggrannare eftersom den drevs av en mycket noggrann kristalloscillator. Man kan i alla fall konstatera att varvtalet varierar mindre än vad tillförd effekt varierar och jag förklarar detta med att friktionen, främst p.g.a. luftmotståndet, ökar med ökande varvtal.
Nummer |
PWM/Effekt |
PIC RPS |
OSC RPS |
6 |
100 % |
83 (100 %) |
89.3 (100 %) |
5 |
95 % |
|
87.7 (98 %) |
4 |
90 % |
|
83.3 (93 %) |
3 |
85 % |
|
80.6 (90 %) |
2 |
80 % |
|
78.1 (87 %) |
1 |
75 % |
|
74.6 (84 %) |
0 |
0 % |
0 (0 %) |
0 (0 %) |
PWM (Pulsviddsmodulering) är ett sätt att omvandla från ett digitalt tal i mikrokontrollern till en analog effekt. Man slår på och av spänningen till den yttre utrustningen och kan variera effekten genom att variera den tid som spänningen är på. Man slår på spänningen med en fix frekvens, i vårt fall 4 kHz, men varierar tiden innan man slår av den linjärt beroende på vilken uteffekt man vill ha. I vårt fall är perioden 250 µs, och vill man t.ex. ha en effekt på 80 % så skall man slå av spänningen efter 200 µs, d.v.s. efter 80 % av periodtiden. Man har då en PWM-signal med en duty-cycle på 80 %. Problemet är att en fläktmotor innehåller spolar och att slå på och av en spänning på 12 V genom dessa ger upphov till självinduktion och kraftiga störningar. Helst hade jag velat driva fläkten med en stabil likspänning.
I projektet använder jag en komponent som klarar att driva en induktiv last och som har en logisk ingång. Det är alltså ingen enkel analog effekttransistor utan en MOSFET-transistor för krävande miljöer med inbyggd driv- och skyddselektronik. Transistorn är ESD-, temperatur- och överströmsskyddad och är försedd med en clamp-diod mellan drain och source. Typen är IPS021 (Intelligent Power Switch) av märket International Rectifier. Jag inser nu att man enklare hade kunnat bli av med störningarna om man hade valt en analog fälteffekttransistor och gjort om styrsignalen till en stabil likspänning. Troligen hade en PowerMOS-transistor fungerat utmärkt. Med denna störningsfria lösning förutser jag ett problem med att få den slutgiltiga effekten som en linjär funktion av PWM duty-cyclen.
I kretsen används en avstämd IR-sensor med inbyggd förstärkare och komparator. Den reagerar endast på IR-ljus på ca 940 nm som är modulerat med en frekvens omkring 38 kHz med 50 % duty-cycle och går då låg normalt ligger utgången hög. Räckvidden är ca 8 m med en standardsändare i en standardomgivning. IR-sensorn är av märket Everlight, typ IRM-8601S eller IRM-8608S. I projektet använde jag inte den i databladet rekommenderade kopplingen med resistor och kondensator. Möjligen hade denna förhindrat en del störningar.
Fjärrkontrollen hör till en mikrostereo av märket Samsung, typ MM-26. På Samsungs hemsida finns en application note för deras mikrokontroller KS57 som handlar om mottagning och sändning av IR-signaler. Jag antar att de använder samma modulationsfrekvens och ett liknande protokoll i stereofjärrkontrollen som i application notet. I dokumentet är modulationsfrekvensen 38 kHz.
Jag undersökte även protokollet med ett digitalt oscilloskop. När man trycker på en knapp skickas ett pulståg på ca 60 ms och det sänds så vitt jag vet endast en gång. Fortsätter man att hålla nere knappen sänds ett kortare pulståg på 9 ms upprepade gånger. Startvillkoret består av en signal på 3.3 ms. Sedan följer långa och korta pauser med en signal på 0.4 ms emellan. En lång paus + signal är 1.8 ms och en kort paus + signal är 0.5 ms. En lång paus + signal tolkar jag som en 1:a och en kort paus + signal som en 0:a.
Jag skrev ett program (mbirrx) som tog emot pulståget för varje knapp och skrev ut det med hexkod på en LCD. Koden är E1654010XXXX02 för varje knapp och upprepningskoden är 03.
|
|
Här visas kopplingsschema för kretsen som kopplas till ett modifierat Microchip PICDEM 2-kort. På PICDEM 2-kortet måste man löda av benet på R19 närmast RC2/CCP1. Lysdioderna B0 B7 finns på PICDEM 2-kortet, och där finns även anslutning för LCD.
Naturligtvis behöver man inte använda PICDEM 2 utan man kan bygga en stand-alone version av projektet. Komponentlista för spänningsreglering: U1=7805 (12.00 kr), C1=10 µF, C2=10 µF. Keramisk resonator med integrerade kondensatorer: XT1 inkl. 2 st. Cosc = Murata CST4,00MGW (8.70 kr). Lysdioder: B0 B7 = 8 st. LED med passande resistornät.
I denna sektion förmedlas kunskaper som gör det lättare att förstå programmet.
MainLoop tar 10 ms p.g.a. att subrutinen LoopTime väntar tills 10 ms har gått sedan den anropades senast. LoopTime utnyttjar då att ett Timer2-avbrott räknar ned en räknare, när räknaren slår över så har det gått 10 ms och subrutinen LoopTime avslutas. MainLoop anropar Blink som anropar MeasureFq var 50:e gång, d.v.s var 500:e ms. Timer0 är konfigurerad att räkna pulser på ingången RA4. I MeasureFq flyttas innehållet i Timer0 till W och RPSL, därefter nollställs timern och RPSH. RPSH räknas upp i Timer0:s overflow-avbrott, men eftersom fläkten skulle behöva rotera så snabbt som 15300 varv/min så används inte RPSH i detta system. Anledningen till att Timer0 innehåller varv/s trots att dessa endast räknas under en halv sekund är att fläkten ger två pulser per varv.
I subrutinen Blink så togglas BLNKSTATUS på och av var 500:e ms. För att indikera läge off med 10 % duty-cycle så krävdes lite extra kod. Om BLNKSTATUS precis gått på och PWR är 0 så skall Power-indikatorn släckas redan efter 100 ms. Detta åstadkoms genom att sätta en flagga TURNOFF. Så länge TURNOFF är satt så testas i BlinkEnd om det gått 100 ms, är så fallet nollställs TURNOFF och Power-indikatorn släcks genom att skriva RPSL till PORTB.
I avsnittet om CPU-fläkten så förklaras PWM i allmänhet och i detta avsnitt förklaras hur det implementeras i PIC:en. PWM-regleringens utsignal på PIC:ens ben RC2/CCP1 är kopplad till effekttransistorn.
Timer2 används både för att generera avbrott för varje 2 ms och för att generera en PWM-period på 250 µs, motsvarande en frekvens på 4 kHz. Prescalern till Timer2 delar den interna oscillatorn på 1 MHz med 1 och postscalern delar komparatorsignalerna med 8. Komparatorn jämför TMR2 med PR2 som innehåller 249; vid likhet nollställs TMR2, postscalern räknas upp samt PWM-perioden börjar om. Detta innebär att komparatorn triggar var 250:e µs. För var åttonde uppräkning av prescalern så genererar den ett Timer2-avbrott, d.v.s. var 2:a ms. PWM-signalens duty-cycle beror på att signalen slås av efter en bestämd tid. Duty-cyclen har en 10-bitars upplösning eftersom de 8 bitarna från TMR2 utvidgas med 2-bitarsdelaren av den yttre klocksignalen, Q-counter. Denna 10-bitars räknare räknas upp från 0 till 999 med hastigheten 4 MHz och jämförs med 10-bitarstalet som utgörs av CCPR1L, CCP1X och CCP1Y. Detta motiverar initieringen av T2CON och den krångliga subrutinen UpdatePWM.
Duty-cyclerna lagras i en tabell, LookupPWM2. Tabellen hanteras av subrutinen LookupPWM som fyller PWMH och PWML med olika värden beroende på DutyCPtr, vilken har det aktuella varvtalsnumret: 0 6.
I avsnittet om fjärrkontrollen så förklaras vilka pulståg den skickar. IR-sensorn är inkopplad på RC1/CCP2 på PIC:en. Timer1, vilken räknar med full hastighet, 1 MHz, används för tidtagning av händelser på CCP2. Mottagningen av ett pulståg från fjärrkontrollen befinner sig i olika tillstånd: WaitnStart (waiting for start condition), RcvStart (receiving start condition) och RcvBit (receiving bit). Först konfigureras CCP2 för att vänta på startvillkoret, d.v.s. på signal från IR-sensorn. När den kommer övergår man till att vänta på paus och tar tiden tills detta inträffar. Om tiden var kortare än 2 ms betraktas det som en störning och man börjar om från början, annars börjar man mäta tiden för första biten, vilken består av en paus (hög) följt av en signal (låg). Biten är färdigmottagen när benet CCP2 går hög nästa gång. Om mottagningstiden för biten var kortare än 1.5 ms så var biten en 0:a annars en 1:a. När pulståget är mottaget inträffar inte fler förändringar av CCP2 och efter ett tag inträffar en timeout beroende på att Timer1, vid starten av varje tidmätning, konfigureras att generera ett avbrott efter 6 ms. Befann sig pulstågsmottagningen i tillståndet RcvBit så sätts en flagga som gör att de mottagna bitarna tolkas av subrutinen UpdateLCD i nästa MainLoop-varv. Subrutinen kallas så av historiska skäl, men fortfarande skriver subrutinen ut debuginformation på en LCD om man skulle ha en sådan ansluten.
De mottagna bitarna sparas i RXBUF 8 bitar i varje byte, och först mottagna bit är mest signifikant. När en byte är full fortsätter programmet att fylla nästa och så vidare. Detekteringen av att en byte är full sker genom att man laddar byten med 1 och när denna skiftas ut i Carry så vet man att byten är full. Indirekt adressering med hjälp av FSR (File Select Register) och INDF används.
Ett problem med denna mottagningsmetod är att de koder för fjärrkontrollen jag angett kanske inte stämmer med de som tillverkaren anger. Det beror dels på att startvillkoret inte bara består av en signal utan troligen även av den efterföljande pausen, dels på att metoden att förladda med 1 innebär att om inte pulståget består av ett helt antal bytes så kommer denna 1:a att finnas med i den sist mottagna byten. Detta spelar dock ingen roll för mottagningen och det går att översätta koder på ett entydigt sätt mellan systemen.
Vid Capture, d.v.s. vid slutet av viss signal eller paus, så sparas uppmätt tid i CCPR2H och CCPR2L, dessutom inträffar ett Capture2-avbrott. Denna tillämpning ställer inte så höga krav på tidmätning och därför används endast CCPR2H. Rent tekniskt hanteras de olika tillstånden, som pulstågsmottagningen kan befinna sig i när ett Capture-avbrott inträffar, av en hopptabell, JmpOnIRState, vilken hoppar till olika subrutiner beroende på IRState.
Detta system för att ta emot och tolka IR-pulståg kan lätt anpassas till andra fjärrkontroller genom att endast ändra tidskonstanterna och kommandokoderna. Dock måste man ta hänsyn till att vissa fjärrkontroller skickar flera identiska pulståg för en knapptryckning. Då skall man endast utföra kommandot om pulståget avkodas på samma sätt alla gångerna.
;Project on "Mikrodatorteknik HT02" ;Version: 1.0beta2 ;Copyright (C) 2002 by Mikael Bonnier, Lund, Sweden. ;E-mail: mikael.bonnier@gmail.com ;Web: http://www.df.lth.se.orbin.se/~mikaelb/ ; ;The program recieves and outputs remote control commands on a display. ;The commands affects a PWM output, which controls a power transistor, ;which controls power to a CPU fan. The power causes the fan to rotate with ;a speed proportional to the power. The fan RPS is counted and output in ;binary on LEDs. ; ;Features: ;1. When the program is running a LED blinks with 1 Hz, 50 % duty cycle. ;2. When a command is accepted a LED blinks briefly. A different ; LED for each command. ;3. When "off" the running LED blinks with a 10 % duty cycle. ; ;Bugs & Limitations: ;1. The fan RPS is incorrect at low speeds due to the fan or interference ; from PWM output frequency. ;2. Necessary to modify PICDEM-2 board by removing resistor R19 leg closest ; to RC2. ;3. Works only with a Samsung stereo remote control. ; ;Todo: ;1. Implement regulated fan speeds i.e. choose RPS and fan speed approaches it. ; ;Commands: ;Power: toggles power to fan on or off ;Volume+: Increases speed one step ;Volume-: Decreases speed one step list P=16C74A, F=INHX8M, C=160, N=80, ST=OFF, MM=OFF, R=DEC title "mbproj1.asm" include <P16C74A.INC> __config (_CP_OFF & _PWRTE_OFF & _XT_OSC & _WDT_OFF & _BODEN_OFF) errorlevel -302 ;Ignore "error" when storing to bank 1 ;;;;;;; Equates ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Freq equ 4 ;Crystal frequency in MHz (4, 10, or 20) Bank0RAM equ H'20' ;Start of Bank 0 RAM area MaxCount equ D'50' ;50 Number of loops in half a second DQ equ 0 ;Display queue, bit 0 of FLAGS PWR equ 1 ;Power on, bit 1 of FLAGS TURNOFF equ 2 ;Turn off LED, bit 2 of FLAGS BLNKSTATUS equ 3 ;BLNKSTATUS, bit 3 of FLAGS ;IR receiver CapFEdge equ B'00000100' ;Capture falling edge CapREdge equ B'00000101' ;Capture rising edge WaitnStart equ 0 ;IRState value RcvStart equ 1 ;IRState value RcvBit equ 2 ;IRState value Tmr1Z equ D'256'-D'23' ;Timer1 interrupt in 6 ms MinStart equ 7 ;2 ms (start = 3.3 ms) MaxLen0 equ 6 ;1.54 ms (bit0 = 0.88 ms, bit1 = 2.2 ms) ;;; ;PWM NODutyC equ D'7' ;No of elements in PWM table ;;; ;LCD Inner equ D'255' ;255 loopcount in 15ms delay Outer equ D'20' ;98 loopcount in 15ms delay, 20 Mhz RWHI equ B'00000100' ;Set R/W high LCD8B equ B'00110000' ;LCD in 8bit mode DISPON equ B'00001100' ;Display on CL_HOME equ B'00000001' ;Clear, go home ADR_INC equ B'00000110' ;Increment adress m. ;;; ;;;;;;; Macros ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; writeLowNibble macro andlw H'0F' addlw 6 btfsc STATUS,DC addlw A'A'-A'0'-H'A' addlw A'0'-6 call Write endm wrReg macro register swapf register,W writeLowNibble movf register,W writeLowNibble endm NEqGoto macro byte,address movlw byte xorwf INDF,W btfss STATUS,Z goto address endm EqGoto macro byte,address movlw byte xorwf INDF,W btfsc STATUS,Z goto address endm LCDData macro bsf PORTA,1 ;Data RS=1, command RS=0 endm LCDCmd macro bcf PORTA,1 ;Data RS=1, command RS=0 endm ;;;;;;; Variables ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; cblock Bank0RAM W_TEMP ;Temporary storage for W during interrupts STATUS_TEMP ;Temporary storage for STATUS during interrupts SCALER ;Scale-of-5 scaler used by LoopTime subroutine BLNKCNT FLAGS RPSH RPSL TDELAY0 ;counter in delay loop TDELAY1 ;counter in delay loop PWMH PWML DutyCPtr IRState RXENDBUF RXBUF: 10 endc ;;;;;;; Vectors ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; org H'000' ;Reset vector goto Mainline ;Branch past tables org H'004' ;Interrupt vector IntVec goto IntService ;Branch to interrupt service routine ;;;;;;; Tables ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; JmpOnIRState movf IRState,W addwf PCL,F goto WaitForStart goto StartRcvd goto BitRcvd LookupPWM2 addwf PCL,F ;DutyCPtr*2 in W ;0 retlw H'00' ;PWMH 0/1000 = 0 % retlw H'00' ;PWML ;1 retlw H'02' ;PWMH 750/1000 = 75 % retlw H'EE' ;PWML ;2 retlw H'03' ;PWMH 800/1000 = 80 % retlw H'20' ;PWML ;3 retlw H'03' ;PWMH 850/1000 = 85 % retlw H'52' ;PWML ;4 retlw H'03' ;PWMH 900/1000 = 90 % retlw H'84' ;PWML ;5 retlw H'03' ;PWMH 950/1000 = 95 % retlw H'B6' ;PWML ;6 retlw H'03' ;PWMH 1000/1000 = 100 % retlw H'E8' ;PWML ;;;;;;; Mainline program ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Mainline call Initial ;Initialize everything MainLoop call Blink btfsc FLAGS,DQ call UpdateLCD call LoopTime ;Force loop time to be ten milliseconds goto MainLoop ;;;;;;; Initial subroutine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ; This subroutine performs all initializations of variables and registers. Initial ;Initialize PIC and LCD. ;;; Assert Freq=4 MHz ;;; ;;; Assert bank 0 ;;; movlw B'00111100' ;Postscaler /8, TMR2ON=1, Prescaler /1 movwf T2CON clrf CCPR1L ;0 % duty cycle clrf CCPR1H movlw B'00001100' ;PWM mode movwf CCP1CON call FalseStart movlw MaxCount ;Initialize first blink of LED movwf BLNKCNT ; to last about 0.50 seconds clrf FLAGS movlw 4 ;Set up Timer2 subroutine movwf SCALER clrf PORTB clrf PORTC ;;; LCD init ;;; clrf PORTA clrf PORTD ;;; -------- ;;; bsf STATUS,RP0 ;Set register access to bank 1 ;;; Assert bank 1 ;;; movlw D'249' movwf PR2 ;PWM freq = 4 kHz bsf TRISC,1 ;Set up RC1/CCP2 as input bcf TRISC,2 ;Set up RC2/CCP1 as output clrf TRISB ;Set up all bits of PORTB as outputs bsf OPTION_REG,T0CS ;Use RA4/T0CKI to count CPU-fan rps bsf OPTION_REG,PSA ;Presc to WDT, i.e. TMR0 without presc bsf PIE1,TMR2IE ;Enable interrupt path bsf PIE1,TMR1IE ;Enable interrupt path bsf PIE2,CCP2IE ;Enable interrupt path ;;; LCD init ;;; clrf TRISD ;Make PORTA and PORTD outputs clrf TRISA movlw B'11111111' movwf ADCON1 ;If PORTA is not in ADC mode ; power up digital outputs. bcf STATUS,RP0 ;Set register access back to bank 0 ;;; Assert bank 0 ;;; call Time15 ;Be sure LCD module ready movlw RWHI ;Sets R/W high movwf PORTA movlw LCD8B ;Operating 8 bit mode. call Write movlw LCD8B ;Do it again call Write movlw DISPON ;Display on call Write movlw CL_HOME ;Clear display, goto home pos call Write movlw ADR_INC ;Automatic increment of address call Write LCDData movlw A'H' call Write movlw A'I' call Write ;;; -------- ;;; call Power bcf PIR2,CCP2IF ;Clear interrupt flag (for safety) bsf INTCON,T0IE ;Enable Timer0 interrupt source bsf INTCON,PEIE ;Enable Timer2 interrupt source bsf INTCON,GIE ;Enable global interrupts return ;;;;;;; PWM subroutines ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ; LookupPWM bcf STATUS,C rlf DutyCPtr,W call LookupPWM2 movwf PWMH rlf DutyCPtr,W addlw 1 call LookupPWM2 movwf PWML return UpdatePWM wrReg DutyCPtr wrReg PWMH wrReg PWML rrf PWMH,F ;Rotate bit 8 rrf PWML,F ; into PWML[7] rrf PWMH,F ;Rotate bit 0 into PWMH[7] rrf PWML,F ; and bit 9 into PWML[7] rrf PWMH,F ; and bits 1,0 into PWMH[7:6] ;Upper 8 bits are now in PWML ;Lower 2 bits are in PWMH[7:6] rrf PWMH,F ;Move bits 1,0 to align with CCP2CON rrf PWMH,W ; and move to W xorwf CCP1CON,W ;Toggle if CCP2X:CCP2Y differ andlw B'00110000' ;Force other bits to zero xorwf CCP1CON,F ;Change bits that differ movf PWML,W ;Move upper eight bits movwf CCPR1L return ;Fourteen cycles ;;;;;;; Blink subroutine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ; This subroutine toggles a LED every 0.5 second. Blink decfsz BLNKCNT,F ;Decrement loop counter and return if not zero goto BlinkEnd movlw MaxCount ;Reinitialize BLNKCNT movwf BLNKCNT movlw 1<<BLNKSTATUS xorwf FLAGS,F call MeasureFq ;Called each 0.5 s btfsc FLAGS,BLNKSTATUS iorlw 1<<7 movwf PORTB btfsc FLAGS,PWR goto BlinkEnd btfsc FLAGS,BLNKSTATUS bsf FLAGS,TURNOFF BlinkEnd btfss FLAGS,TURNOFF return movlw MaxCount-10 xorwf BLNKCNT,W btfss STATUS,Z return bcf FLAGS,TURNOFF movf RPSL,W movwf PORTB return ;;;;;;; MeasFreq subroutine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; MeasureFq movf TMR0,W ;Fan gives 2 pulses per rotation movwf RPSL clrf TMR0 clrf RPSH return ;;;;;;; LoopTime subroutine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ; This subroutine first waits, for however long is necessary, for Timer2 and ; the Timer2 subroutine in IntService to indicate that ten milliseconds have ; passed since the last time that they indicated this. LoopTime btfss SCALER,7 ;Wait for 00000000 to 11111111 change goto LoopTime movlw 5 ;Add 5 to SCALER addwf SCALER,F return ;;;;;;; LCD subroutines ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; UpdateLCD bcf FLAGS,DQ LCDCmd movlw CL_HOME ;clear display, goto home pos call Write LCDData incf FSR,W movwf RXENDBUF movlw RXBUF movwf FSR ChkPattern NEqGoto H'E1',ChkRepeat incf FSR,F NEqGoto H'65',UnknRemote incf FSR,F NEqGoto H'40',UnknRemote incf FSR,F NEqGoto H'10',UnknRemote incf FSR,F ;Cmd byte 1 here incf FSR,F ;Cmd byte 2 here incf FSR,F NEqGoto H'02',UnknRemote WhichCmd movlw RXBUF+4 movwf FSR NEqGoto H'00',Next1 incf FSR,F EqGoto H'10',Power Next1 btfss FLAGS,PWR goto FalseStart ;When power is off, skip other commands. movlw RXBUF+4 movwf FSR NEqGoto H'02',Next2 incf FSR,F EqGoto H'30',VolPlus Next2 movlw RXBUF+4 movwf FSR NEqGoto H'06',Next3 incf FSR,F EqGoto H'70',VolMinus Next3 UnknCmd movlw A'C' call Write bsf PORTB,2 goto FalseStart UnknRemote movlw A'U' call Write bsf PORTB,1 call LookupPWM call UpdatePWM goto FalseStart ChkRepeat movlw H'03' xorwf INDF,W btfss STATUS,Z goto UnknRemote Repeat movlw A'R' call Write bsf PORTB,3 call LookupPWM call UpdatePWM goto FalseStart Power movlw A'P' call Write bsf PORTB,6 movlw (NODutyC>>1) + 1 movwf DutyCPtr btfsc FLAGS,PWR clrf DutyCPtr movlw 1<<PWR xorwf FLAGS,F call LookupPWM call UpdatePWM goto FalseStart VolPlus movlw A'+' call Write bsf PORTB,5 movlw NODutyC-1 ;Increase DutyCPtr but within 0..NODutyC-1 xorwf DutyCPtr,W btfss STATUS,Z incf DutyCPtr,F call LookupPWM call UpdatePWM goto FalseStart VolMinus movlw A'-' call Write bsf PORTB,4 movf DutyCPtr,F ;Decrease DutyCPtr but within 0..NODutyC-1 btfss STATUS,Z decf DutyCPtr,F call LookupPWM call UpdatePWM goto FalseStart Write ;pulses controlling the LCD data and instruction comm. movwf PORTD ;Sets the levels of d0-d7 bcf PORTA,2 ;Set R/W low, i.e. write to LCD bsf PORTA,3 ;Set enable high bcf PORTA,3 ;Set enable low, i.e. ; now d0-d7 are captured by LCD. bsf PORTA,2 ;Set R/W hi for next write seq. call Time15 ;Wait until talking to lcd next time. return Time15 ; 15 millisecs (appr) wait movlw Outer movwf TDELAY0 Time150 movlw Inner movwf TDELAY1 Time151 decfsz TDELAY1, F goto Time151 decfsz TDELAY0, F goto Time150 return ;;;;;;; IntService interrupt service routine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ; This interrupt service routine fields all interrupts. It first sets aside W ; and STATUS. It assumes that direct addressing will not be used in the ; mainline code to access Bank 1 addresses (once the Initial subroutine has ; been executed and interrupts enabled). It polls each possible interrupt ; source to determine whether it needs service. IntService ; Set aside W and STATUS movwf W_TEMP ;Copy W to RAM swapf STATUS,W ;Move STATUS to W without affecting Z bit movwf STATUS_TEMP ;Copy to RAM (with nibbles swapped) ; Execute polling routine ;;; Assert bank0 ;;; btfsc PIR2,CCP2IF ;Check if interrupt from IRRX call Capture2 btfsc PIR1,TMR1IF ;Check if Timer1 timout call Timer1 btfsc INTCON,T0IF ;Check if RPS-meter Timer0 overflow call Timer0 ;If more than 255 rps btfsc PIR1,TMR2IF ;Check if Timer2 timeout call Timer2 ;Happens every 10 ms ; btfsc ... ;Check another interrupt source ; call ... ;If ready, service it ; Restore STATUS and W and return from interrupt swapf STATUS_TEMP,W ;Restore STATUS bits (unswapping nibbles) movwf STATUS ; without affecting Z bit swapf W_TEMP,F ;Swap W_TEMP swapf W_TEMP,W ; and swap again into W without affecting Z bit retfie ;Return to mainline code; reenable interrupts ;;;;;;; Timer0 subroutine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Timer0 bcf INTCON,T0IF ;Clear interrupt flag (Bank 0) incf RPSH,F return ;;;;;;; Timer2 subroutine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ; This subroutine, called from within IntService, clears the Timer2 flag, ; decrements SCALER for use by LoopTime subroutine in the mainline code, and ; returns. Timer2 bcf PIR1,TMR2IF ;Clear interrupt flag (Bank 0) decf SCALER,F return ;;;;;;; Capture2 subroutine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ; This subroutine, called from within IntService, clears the Capture1 flag. Capture2 call JmpOnIRState bcf PIR2,CCP2IF ;Clear interrupt flag (Bank 0) return WaitForStart movlw RcvStart movwf IRState TriggOnHi clrf CCP2CON ;Turn CCP module off clrf TMR1L movlw Tmr1Z movwf TMR1H bsf T1CON,TMR1ON movlw CapREdge movwf CCP2CON return StartRcvd bcf T1CON,TMR1ON movf CCPR2H,W sublw MinStart+Tmr1Z btfsc STATUS,C ;C=0 if borrow goto FalseStart TrueStart movlw RcvBit ;CCPR2H > MinStart movwf IRState call RstQueue goto TriggOnHi FalseStart ;CCPR2H <= MinStart movlw WaitnStart movwf IRState TriggOnLo clrf CCP2CON ;Turn CCP module off movlw CapFEdge movwf CCP2CON return BitRcvd movlw RcvBit movwf IRState movlw MaxLen0+Tmr1Z subwf CCPR2H,W ;Result of CCPR2H >= MaxLen0 in the Carry bit call Enqueue goto TriggOnHi RstQueue movlw RXBUF movwf FSR goto PrepByte Enqueue ;Received bit in Carry bit rlf INDF,F btfss STATUS,C return incf FSR,F PrepByte movlw B'00000001' movwf INDF return Timer1 ;Timeout bcf T1CON,TMR1ON ;Shut off Timer 1 bcf PIR1,TMR1IF ;Clear interrupt flag movf IRState,W xorlw RcvBit ;Set Z if IRState == RcvBit btfss STATUS,Z goto FalseStart ;If IRState != RcvBit goto FalseStart else clrf CCP2CON ;Turn CCP module off bcf PIR2,CCP2IF ;Clear interrupt flag (for safety) bsf FLAGS,DQ ;Display Queue in MainLoop return end