Forum: Mikrocontroller und Digitale Elektronik PWM mit PIC18LF23K22


von Marco (Gast)


Angehängte Dateien:

Lesenswert?

Hallo liebe Community,
ich bin Quereinsteiger bei der PIC-Programmierung. Ich habe Erfahrungen 
bei der Programmierung eines PIC16 in Assembler und möchte mich nun an 
einem PIC18 mit C-Programmierung versuchen. Dafür habe ich mir vom 
Franzis-Verlag ein Lernpaket für PIC-µC gekauft und versuche nun damit 
ein wenig eigenständig zu arbeiten.

Leider stehe ich bei der Ausgabe eines PWM-Signals völlig auf dem 
Schlauch und würde mich sehr freuen, falls mir jemand helfen könnte!

Probleme:
1) im Datasheet 
(http://ww1.microchip.com/downloads/en/DeviceDoc/41412E.pdf) auf Seiten 
184 ff. ist unter dem Kapitel 14.3 das Vorgehen (eigentlich) 
beschrieben.

Unter dem Punkt 14.3.2 ist beschrieben, dass ich je nach TMRx 
(TMR2,TMR4,TMR6) in den Registern CCPTMRSx die Bits setzen soll. Leider 
kann ich damit nichts anfangen; welche der 5 möglichen Bit-Paare soll 
ich für die Nutzung welches Timers setzen ... ?

2) Ich habe gelesen, dass die PWM-Ausgänge gemultiplext werden müssen in 
der Config-Datei. Wie genau soll das funktionieren ... ?

Meinen bisherigen Code habe ich einfach mal nach bestem Gewissen 
erstellt, leider funktioniert bisher nur, dass die LED nach ein paar 
Sekunden Verzögerung einmalig angeht, stattdass ein Sinus o.Ä. 
durchlaufen wird .... Wenn ich mir über das Watch-Fenster die Werte 
anzeigen lassen, werden die Werte des Sinus "eigentlich" in das CCPR5L 
Register übernommen und naja, ich weiß leider nicht weiter!

Vorab vielen Dank für jegliche Hilfestellung!

PS: Leider habe ich keine klare Vorgabe bzgl. der Vorgehensweise mit dem 
Code gefunden und füge ihn daher sowohl als Text als auch als Datei an.
1
/** I N C L U D E S *******************************************/
2
#include <htc.h>
3
#include <pic18f23k22.h>
4
#include "Config.h"      //Konfigurationsbits
5
6
7
/** D E F I N E S *********************************************/
8
#define TASTER     PORTBbits.RB0  //Taster liegt an Port B RB0
9
#define LED      LATAbits.LATA4  //LED liegt an Port A RA4
10
11
/** P R O T O T Y P E S ***************************************/
12
void   interrupt isr_high(void);
13
void   interrupt low_priority isr_low(void);
14
void   delay( int ms );
15
void   setupTouch(void);
16
int   getKey( char key );
17
void   setLED( short light );
18
19
/** I N T E R R U P T S ***************************************/
20
void interrupt isr_high(void) {
21
22
}
23
24
void interrupt low_priority isr_low(void) {
25
26
}
27
28
/** H A U P T P R O G R A M M *********************************/
29
void main(void)
30
{
31
  char button=0;
32
  volatile unsigned char sinus[40]={127,147,166,185,202,217,230,240,248,252,254,252,248,240,230,217,202,185,166,147,127,107,88,69,52,37,24,14,6,2,0,2,6,14,24,37,52,69,88,107}; //Array mit berechneten Sinuswerten
33
  volatile char a=1;   //Zählervariable zum Auslesen des Arrays
34
  short sin_wert=0;
35
36
  //Port A
37
  LATA = 0x00;
38
  TRISA = 0x00;    //Alle Pins von Port A sind Ausgänge
39
  ANSELA = 0x00;    //Alle Pins von Port A sind digitale I/O's
40
  //Port B
41
  LATB = 0x00;
42
  TRISB = 0xFF;    //Alle Pins von Port B sind Eingänge    
43
  ANSELB = 0x00;    //Alle Pins von Port A sind digitale I/O's
44
  //Port C
45
  LATC = 0x00;
46
  TRISC = 0xB0;    //RC0..RC3 = Ausgänge, RC4..RC7 = Eingänge
47
  ANSELC = 0x30;    //nur RC4 und RC5 sind analoge Eingänge
48
49
50
  while(1) {          //Hauptschleife  
51
    if(a==40){
52
      sin_wert = sinus[40];
53
          delay(10);
54
      a=1;
55
    }
56
    
57
    else{
58
      sin_wert = sinus[a];
59
          delay(10);
60
      a++;
61
    }  
62
    setLED(sin_wert);
63
  }  
64
}
65
66
//Zeitverzögerung
67
void delay( int ms ) {
68
  int i=0;
69
  //1 ms = 4000 Befehlstakte (16 MHz Quarz / 4)
70
  for( i=0; i<ms; i++ ) {
71
    _delay(10);
72
  }   
73
}
74
75
//Setzt die Helligkeit der LED über PWM
76
void setLED( short light ) {
77
  
78
  TRISAbits.TRISA4=1;    //als Eingang schalten
79
  CCPTMRS0 = 0x00;      //Timer 2 wird für die PWM verwendet
80
  CCPTMRS1 = 0x00;
81
  PR2 = 0x63;          //für PWM mit 10 kHz (T=100us)
82
  T2CON = 0x01;        //Prescaler 1:4
83
84
  CCP5CON = 0x0C | (light&0x03);  //PWM-Mode und untere
85
                          //2 Bits der Pulsbreite
86
  CCPR5L = light >> 2;  //oberen 8 Bits der Pulsbreite
87
  //PIR4 = 0x00;
88
  T2CONbits.TMR2ON=1;      //Timer 2 starten
89
  //while(PIR4bits.CCP5IF);    //warten bis der Timer überläuft
90
  TRISAbits.TRISA4=0;      //als Ausgang schalten und
91
                    //PWM freigeben
92
  return;
93
}

von Michael S. (rbs_phoenix)


Lesenswert?

Marco schrieb:
> Unter dem Punkt 14.3.2 ist beschrieben, dass ich je nach TMRx
> (TMR2,TMR4,TMR6) in den Registern CCPTMRSx die Bits setzen soll. Leider
> kann ich damit nichts anfangen; welche der 5 möglichen Bit-Paare soll
> ich für die Nutzung welches Timers setzen ... ?

Du kannst in den Registern CCPTMRSx wählen, welcher Timer für welchen 
PWM-Ausgang zuständig ist (TMR2/4/6). So hast du 3 voneinander 
unabhänige PWM-Ausgänge, die halt unterschiedliche Frequenzen und 
Duty-Cycles haben können. Mit dem CCPRxL Register und den beiden Bits im 
CCPxCON-Register kannst du die Duty-Cycle-Ratio einstellen, also wieviel 
High und wieviel Low pro Periode sein soll. Mit dem Vorteiler des Timers 
und dem PRx Register stellst du die Frequenz ein.

Marco schrieb:
> 2) Ich habe gelesen, dass die PWM-Ausgänge gemultiplext werden müssen in
> der Config-Datei. Wie genau soll das funktionieren ... ?

Du kannst in der Konfiguration einstellen, wo P2B ist (für Single-Mode 
uninteressant), wo CCP2 und wo CCP3 sein soll. Genauer gesagt im 
High-Byte des dritten Configuration Register.


Zum Code:
Ansich initialisiert man einmal am Anfang das PWM-Modul. Also CCPxCON, 
CCPTMRSx, PRx, Timer-Prescaler usw. Fürs Faden wird das ansich ja nicht 
mehr geändert, zumindest in deinem Fall erstmal unnötig. Danach 
schraubst du einfach die Dutycyleratio hin und her. Also einmal 
Configurieren und dann die Werte aus dem Array direkt in das CCPRxL 
schreiben. Sprich, alles was da steht (bis auf die Zeile mit dem CCPR5L) 
gleich unter die Main. Und dann kannst du, statt es in sin_wert zu 
schreiben, direkt in das CCPR5L schreiben. Die Funktion kannst du dir 
dann sparen.

Tip: Ich mache es immer so, das ich eine init()-Funktion habe. Da kommt 
alles rein, TRISA..., ADCON, CCP, CMx.. TxCON, OSCCON usw. Die kommt bei 
mir dann direkt unter das "void main(){"

OSCCON seh ich bei dir z.B. nicht. Klar hat das Register Reset-Werte, 
aber die passen bei mir meist nicht zu dem was ich brauche und dazu 
fühle ich mich sicherer, wenn ich alles nochmal explizit hinschreibe. 
Das sind wenige µs einmalig beim "Hochfahren".

von Marco G. (marco_gr)


Lesenswert?

Hallo und vorab vielen Dank für deine Antwort,
bevor ich wieder Unsinn fabriziere:
Wo genau ist denn beschrieben welchem PWM-Ausgang ich welchen Timer 
zuordne .. ? Es gibt ja nur TMR2,TMR4 und TMR6. Jedoch die Register 1-5, 
so dass sich mir leider spontan kein logischer Zusammenhang offenbart. 
Wenn es "nur" 1-4 geben würde, würde ich eine Addition vermuten aber so 
bin ich leider einfach ratlos wie ich die Register setzen muss, um 
einfach TMR2 meinem PWM-Signal des CCP5 zuzuordnen.

Ich dachte die Wahl des Oszillators sei einmalig mit der 
Konfigurationsdatei abgedeckt und man kann nur den TMR0 nochmal separat 
auf einen "anderen" Oszialltor legen? Oder geht das bei dem PIC18 nun 
auch mit den anderen Timern?

Vielleicht könntest du mir als kleine Hilfestellung eine von dir 
kommentierte init() Datei bzw. ein PWM-Beispiel für einen PIC18 in C 
anhängen, damit ich mich ein wenig orientieren kann?
Ich finde im Internet leider so wenige konkrete Beispiele, die nicht in 
Assembler programmiert sind und habe deshalb leider nur diese 
Flickeschusterei von oben zustandegebracht ....

Vorab vielen Dank!

von Michael S. (rbs_phoenix)


Lesenswert?

Sorry, ich hab total verpeilt, dass du nochmal geschrieben hast.

Also:

Marco G. schrieb:
> Es gibt ja nur TMR2,TMR4 und TMR6. Jedoch die Register 1-5,
> so dass sich mir leider spontan kein logischer Zusammenhang offenbart.

Du hast die beiden Register CCPTMRS0 und CCPTMRS1 (Seite 208).
Dort kannst du sagen, welcher CCP welchen Timer als Grundlage hast. Wenn 
du zum Beispiel nur den CCP5 brauchst und Timer 2 als Basis haben 
willst, brauchst du einfach nur dafür sorgen, dass Bit 2+3 vom Register 
CCPTMRS1 00 sind. Also z.B. "CCPTMRS1 = 0;".
Da steht ja als Erklärung:
1
bit 3-2 C5TSEL<1:0>: CCP5 Timer Selection bits
2
   00 = CCP5 – Capture/Compare modes use Timer1, PWM modes use Timer2
3
   01 = CCP5 – Capture/Compare modes use Timer3, PWM modes use Timer4
4
   10 = CCP5 – Capture/Compare modes use Timer5, PWM modes use Timer6
5
   11 = Reserved

Mit den beiden Bits sagst du, welcher Timer benutzt werden soll. Und da 
steht dann "PWM modes use Timer2". Das heißt, wenn du CCP5 als 
PWM-Ausgang konfiguriest (im CCP5CON-Register) wird Timer2 benutzt.


Marco G. schrieb:
> Ich dachte die Wahl des Oszillators sei einmalig mit der
> Konfigurationsdatei abgedeckt und man kann nur den TMR0 nochmal separat
> auf einen "anderen" Oszialltor legen? Oder geht das bei dem PIC18 nun
> auch mit den anderen Timern?

Du kannst da Einstellen, ob der PLL Ein- oder Abgeschaltet sein soll 
(Wenn aus, ist er wirklich aus, wenn an, dann kann man ihn trotzdem per 
Register an und ausschalten) und welche Taktquelle er hat. Z.b. Externer 
RC-Oscillator, HS, XT, Intern, usw.
Jetzt hast du gewählt, welche Quelle du haben willst, aber gerade bei 
der Verwendung des internen Oscillators ist das OSCCON-Register 
nützlich. Auf Seite 28 ist die Übersicht des Oscillator-Blocks. Da 
siehst du z.B., dass, wenn man den internen Oscillator benutzt, 
auswählen kann, auf welche Frequenz der interne Takt geteilt werden 
soll. Die Auswahl dazu ist im OSCCON-Register. Mit dem OSCTUNE-Register 
kann man den Internen Takt leicht beeinflussen, um ihn zu Kalibrieren.



Marco G. schrieb:
> Vielleicht könntest du mir als kleine Hilfestellung eine von dir
> kommentierte init() Datei bzw. ein PWM-Beispiel für einen PIC18 in C
> anhängen, damit ich mich ein wenig orientieren kann?

Hier mal meine Init und ein auf die schnelle zusammengeschriebenes 
Programm. Ungetestet aber sollte gehen.
1
void init(){
2
  OSCCON = 0b01100010;   // Hier: 8MHz Interner Oscillator
3
  ADCON0 = 0;            // ADC-Kanäle Ausschalten
4
  ADCON1 = 0;
5
  ANSELA = 0;
6
  ANSELB = 0;
7
  DACCON0 = 0;           // DAC Ausschalten
8
  CM1CON0 = 0;
9
  CM2CON0 = 0;
10
  CCP1CON = 0;           // CCP1-4 als Digital IO
11
  CCP2CON = 0;
12
  CCP3CON = 0;
13
  CCP4CON = 0;
14
  CCP5CON = 0b00001100;  // CCP5 als Standard PWM
15
  CCPTMRS1 = 0;          // CCP5 Timer2 zuweisen
16
  T2CON = 0b00000111;    // Timer2 an, Prescaler 16 (Also läuft der Timer mit 500kHz)
17
  PR2 = 124;             // PR2 = 124. Formel auf Seite 187 zur Periodenberechnung -> (124+1)*4*(1/8MHz)*16=1ms-> 1kHz
18
  TRISA = 0b00001111;    // DIO's Einstellen
19
  TRISB = 0b01110111;
20
  TRISC = 0;
21
  TRISE = 0;
22
  PORTA = 0;
23
  PORTB = 0;
24
  PORTC = 0;
25
  PORTE = 0;
26
  LATA = 0;
27
  LATB = 0;
28
  LATC = 0;
29
  LATE = 0;
30
}
31
32
33
void main(){
34
  char mode=0, counter=0;
35
  volatile unsigned char sinus[40]={127,147,166,185,202,217,230,240,248,252,254,252,248,240,230,217,202,185,166,147,127,107,88,69,52,37,24,14,6,2,0,2,6,14,24,37,52,69,88,107}; //Array mit berechneten Sinuswerten
36
  init();
37
  
38
  while(1){
39
    if(mode == 0){ // Sinus ausgeben
40
      CCPR5L = sinus[counter];
41
      if(counter >= 39){
42
        mode = 1;
43
      }
44
      else{
45
        counter++;
46
      }
47
      delay_us(1250); // 1250µs * 40 = 500ms = 0,5s
48
    }
49
    if(mode == 1){
50
      // Mach was danach, z.B. neustarten:
51
      couter = 0;
52
      mode = 0;
53
    }
54
  }
55
}

von Marco G. (marco_gr)


Lesenswert?

Hallo Michael,
vorab vielen Dank für deine Hilfe und die kleine "Erinnerungsmail" ;)

Ich bin leider erst gestern Abend dazu gekommen den Code zu 
implementieren und auszutesten, klappt wunderbar. Also vielen Dank!


Ich bin gerade dabei das PWM-Signal nun auf den Controller 
zurückzukoppeln, indem ich eine Spule mit dem Signal beaufschlage.

Ziel meines kleinen Projektes ist eine Induktivitätsmessung mit 
möglichst einfachen Mitteln. Der obige Ansatz hat noch auf die Messung 
einer Phasenverschiebung bzw. eines frequenzabhängigen Widerstandes 
hinausgezielt, momentan bin ich aber sogar noch mehr angetan von 
folgender Idee:

ich beaufschlage mein RL-Glied mit einem PWM-Signal bzw. mit einer 
Gleichspannung. Beim Zeitpunkt des Beginns der Beaufschlagung starte ich 
einen Counter. An einem CCP-Modul messe ich wann ein Referenzwert 
erreicht wird, zB der Wert von 3 tau. Das Überschreiten dieses Wertes 
stoppt den Counter und ich kann mich rechnerisch über tau = L/(3*R) an 
die Berechnung meiner Induktivität begeben.
In bestimmten Intervallen wird über einen Mess-Shunt eine reine Messung 
des Wirkwiderstandes vorgenommen, um etwaige temperaturbedingte 
Schwankungen auszugleichen. Quasi eine Kalibrierung des Systems.

Was haltet ihr von dieser Idee?
Was sind mögliche Probleme? Ich habe spontan an den Wirkwiderstand 
gedacht, weil tau ja doch "sehr abhängig" davon ist. Sehr ihr noch 
weitere Trouble-Spots?

Vorab vielen Dank für Tipps und nachträglich nochmal vielen Dank für die 
bereits erfolgte Hilfe ;)

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.