PIC 16F684 van knipperlicht naar... (pagina 4)

Hello World!

Schema      JAL code      HEX file


Hello World: de zin waarmee elke startersprogramma zich kenbaar maakt. Geen betere zin om op een LCD display te laten zien die aan onze controller is gekoppeld

Voor we het display beschrijven eerst nog enkele aanpassingen aan het programma. Wegens perikelen met Timer0 (en daaruit voortvloeiende onjuistheden - Timer0 zit enkel nog in de chips voor achterwaartse compatibiliteit) die daardoor niet echt eenvoudig te gebruiken is voor juiste timings, schakelen we over op Timer2 die veel juister en eenvoudiger aan te sturen is.
Ook blijkt de interne oscillator slechts een resolutie van +/- 1% te hebben. Voor een klok is dit niet toereikend.
De aanpassingen zijn te vinden in het schema. Op de pennen 2 en 3 is een 4 MHz kristal en twee condensatoren aanwezig.
De processorinstellingen in het programma moeten we aanpassen:
pragma target OSC XT (bij de interne oscillator was dat pragma target OSC INTOSC_NOCLKOUT).

  pragma target clock 4_000_000 ;oscillator frequency
pragma target OSC XT ;4MHz xTal
pragma target WDT disabled ;no watchdog
pragma target MCLR internal ;reset internally
 

Timer2. Afgestapt van Timer0 om diverse redenen. Na het laden van Timer0 met een waarde, telt die bij iedere (al dan niet prescaler-gedeelde) puls eentje bij. Bij de overgang van 0xFF (255) naar 0x00 (0) wordt de interruptvlag gezet. De timer telt echter gewoon door. De interrupt wordt direct uitgevoerd maar er gaat tijd verloren voor het saven van belangrijke registers (in JAL niet zichtbaar maar wel in de *.asm of *.lst file die door de compiler gegenereerd wordt).
In de interruptroutine moet worden gekeken of de interrupt werd veroorzaakt door Timer0 (terug enkele µs verlies) en dan wordt pas de herlaadwaarde aan de timer toegekend. Na die toekenning duurt het nog 2 cycli (instructietijd * prescaler) eer de timer vanaf die waarde terug herstart. Dit lijkt een µs kwestie (en dat is het ook) maar het is een fout die de interruptfrequentie omlaag haalt en de berekende waarden kloppen niet meer. Door uitgebreide berekeningen van het verlies aan µs door al die processen en die te verwerken in de berekening van de herlaadwaarde kunnen toch een juiste timing geven...

Timer2: De teller telt vanaf 0 en de waarde wordt steeds vergeleken met de waarde in PR2. Wanneer die waarde gelijk is, wordt de postscaler met één verhoogd. Na het volledig doorlopen van de postscaler wordt de interruptvlag gezet. De vertragingen in de interruptroutine spelen geen rol meer, de teller telt tot de ingestelde waarde in PR2 en herstart dan vanaf 0.
Er is wel nog een kleine 'valkuil': Bij de berekenede waarde voor PR2 moet steeds 1 afgetrokken worden voor de vertraging/synchronisatie van de teller!

De klokperiode is 1µs (1/4 van de klokfrequentie 4 MHz / 4 geeft 1µs instructieperiode). De prescaler staat op 16 (16µs per telpuls voor Timer2). De herlaadwaarde van de timer staat op 250 (werkelijke waarde -1!) wat een waarde geeft van 4 ms. De postscaler deelt deze waarde nogmaals door 5 (geeft een 20 ms interrupt).
Die 20 ms is niet toevallig gekozen. We willen namelijk verder bouwen aan dit projectje naar een servotester toe (servo's verlangen een refresh elke 20 ms - 50 Hz).

Verder wordt nog de interrupt voor Timer2 ge-enabled en daar Timer2 deel uit maakt van de periferie moet ook deze periferie-interrupt ge-enabled worden (INTCON_PEIE).
Als volgt nog enkele constanten en variabelen.

  ;-------------------------------------------------------------------------------
;instellingen voor Timer2
;Prescaler= 16
;PostScaler = 5
;PR2 = 250 - 1 voor herlaadtijd
;Freq = 50 Hz
;Tijd = 0.02 seconden
;-------------------------------------------------------------------------------
T2CON_TOUTPS = 0b0100 ;postscaler
T2CON_T2CKPS = 0b11
PR2 = 249 ;herlaadwaarde
PIE1_TMR2IE = high ;Timer2 interrupt enable
INTCON_PEIE = high ;perifierie interrupt enable
PIR1_TMR2IF = low ;wis interrupt vlag

const byte TimerCounterReload = 50
var byte TimerCounter = TimerCounterReload
var volatile byte TimePassed = 0
 

Om data weer te geven door de controller gebruiken we een standaard (HD44780 compatibele) LCD module.
Die module is met de controller verbonden via 6 lijnen: vier voor de data en twee voor de besturingslijnen (handshaking).  De laagste 4 bits van poort C dienen voor de data (verbonden met hoogste 4 databits van de LCD module).  C4 is verbonden met de EN lijn van de module en C5 met de RS pin.
De gebruikte poorten worden als uitgang geconfigureerd.
De gebruikte module is er eentje met 2 regels van elk 20 karakters (wordt vastgelegd met de constanten LCD_ROWS en LCD_CHARS).
In dit voorbeeld gebruiken we een variabele om de string 'Hello World' op te bergen, maar daar de data van die variabele hier niet gewijzigd wordt, is het beter om hiervoor een constante te gebruiken (const byte[] = "Hello World").
Daarna wordt de bibliotheek geladen die alle functies/procedures/instellingen van de LCD module bevat. Na het laden wordt het het LCD geïnitialiseerd.
Een andere interessante bibliotheek wordt ook geladen: 'print'. Deze bibliotheek bevat verschillende procedures om op een eenvoudige manier data op een scherm (of via een seriële verbinding) weer te geven.
Het scherm wordt gewist en de tekst wordt op het scherm weergegeven.

  ;-------------------------------------------------------------------------------
;LCD declaratie en initialisatie
;-------------------------------------------------------------------------------
;Volgende constanten moeten gedeclareerd worden:
const byte LCD_ROWS = 2 -- 1, 2 or 4 lines
const byte LCD_CHARS = 20 -- 8, 16 or 20 chars per line
;Alisassen voor de handshake-lijnen:
alias lcd_rs is pin_c5 -- cmd/data select
alias lcd_en is pin_c4 -- trigger
pin_C5_direction = output
pin_C4_direction
= output
;Aliassen voor de vier datalijnen:
alias lcd_dataport is portC_low -- 4 databits
pin_C0_direction = output
pin_C1_direction
= output
pin_C2_direction
= output
pin_C3_direction
= output

var byte World[11] = "Hello World"

;laad de eigenlijke bibliotheek
include lcd_hd44780_4
;en ionitialiseer het display
lcd_init() -- init the lcd controller
;We laden meteen een andere interessante bibliotheek om eenvoudig tekst
;op het display te krijgen
include print

;Om het scherm toch iets nuttig te laten zien, tonen we de verlopen seconden
var dword dwSeconds = 0

;en we zetten de tekst op het scherm na dit voor alle zekerheid eerst te
;wissen:
lcd_clear_screen()
print_string(lcd, World)
 

In de interruptprocedure wordt nu enkel ingegaan op een interrupt die door Timer2 wordt veroorzaakt (elke 20 ms). Elke seconde wordt dit aan de mainloop doorgegeven via de variabele TimePassed.

  ;-------------------------------------------------------------------------------
;INTERRUPT PROCEDURE
;-------------------------------------------------------------------------------
procedure Timer0Interrupt is
  
pragma interrupt
  
if(PIR1_TMR2IF)then
    
PIR1_TMR2IF = low
    
;20ms
    ;laat dit 50 keer gebeuren voor 1 seconde
    
TimerCounter = TimerCounter - 1
    
if(TimerCounter == 0)then
      
TimerCounter = TimerCounterReload ;herlaad de teller
      
TimePassed = 1 ;tijd is verstreken
    
end if
  end if
end procedure
 

In dit voorbeeld verstrijkt er wel wat tijd vooralleer de mainloop wordt gestart (LCD wordt geïnitialiseerd en tekst op het scherm gezet). Vandaar dat we wachten om de interrupt in te schakelen tot vlak voor die mainloop.
Die mainloop is op zich heel eenvoudig: de led knippert om de seconde en op de 2° regel van het scherm wordt de verlopen tijd (in seconden) weergegeven.

  ;-------------------------------------------------------------------------------
T2CON_TMR2ON = high ;turn timer2 on;
INTCON_GIE = high ;enable interrupts
;-------------------------------------------------------------------------------
forever loop
  if
(TimePassed > 0)then
    
;1 seconde is verstreken
    
TimePassed = 0 ;wis de vlag
    
Led = !Led ;zet de led aan of uit
    ;zet de teller op het LCD lijn 2, 1° positie
    
lcd_cursor_position(1,0)
    dwSeconds = dwSeconds + 1
    
print_dword_dec(lcd, dwSeconds)
  end if
  
;hier tijd voor andere zaken uit te voeren
  ;...
end loop
;-------------------------------------------------------------------------------
 

 


Ook bij dit projectje terug een zijsprong gemaakt en het programma wat uitgebreid door de tekst op de bovenste regel te laten knipperen en de verlopen tijd in het formaat UU:MM:SS weer te geven.

Schema      JAL code




 


(pagina 3) ...PIC 16F684 van knipperlicht naar... (pagina 5)


Links

JALEdit de editor om JAL code te schrijven
JALlib de bibliotheken (en voorbeelden) en de compiler
MPLab IDE de ontwikkel- en PicProgrammeromgeving
Microchip de site van de PIC's
JalList Hulp en hopen voorbeelden