Gefunden
im Netz, gute Lösung: http://www.pololu.com/
http://www.pololu.com/catalog/product/207
Timerpräzise
Multi PWM Erzeugung via Software und "einem" Hardwaretimer
am Beispiel der AVR µController ATMega16 oder AtMega8
Prinzip:
* PWM = Pulsweiten Modulation.
Die
Aufgabe. Mehrere präzise, jitterarme (wackelarme
oder stabile) PWMs für Servos:
Eine präzise Zeitbasis ist erforderlich. Und das ganze eben
sehr sehr
schnell.
Realisation:
1.) 16Mhz ohne Vorteilung in
einen freien „8 Bit Timer“
des µControllers schieben, und damit Overflow Interrupts
auslösen...
merke: 8 Bit = 2^8
=256.
Der Zähler
läuft einfach von 0 bis 255,
und wenn er wieder 0
wird, löst er einen Overflow Interrupt aus.
Simpel
und präzise !
Das hat eine Frequenzteilung zu Folge.
16 MHZ / 256 = 62500
Interrupts / Sekunde !!
Software:
#define TCCR2_CLK_0
0x01
#define TIMSK_TOIE0_FLAG 0x01
TCCR0 = TCCR2_CLK_0;
CLOCK ON. Quarz Clock direkt
ohne Vorteilung an 8-Bit
Timer 0 anlegen
TIMSK |=
TIMSK_TOIE0_FLAG;
// Interrupt EIN
Pro Interrupt stehen also NUR 1/62500
= 0,000016 Sekunden = 16 ^-6
= 16µSekunden Zeit für die Verarbeitung
zur Verfügung.
Das ist nicht viel Zeit. Also geht (diesmal)
Zeitoptimierung vor Eleganz
und Schönheit des Programmierstils.
2.) Was
passiert im Interrupt ?
a) Sagen wir mal am
Zeitpunkt 0
gehen alle PWM Kanäle ohne Bedingung von
0 auf den Wert 1 ( steigende
Flanke )
ab jetzt
tickt die Uhr !
b) Bei JEDEM Interrupt wird ein
schneller keiner
8BitZähler (0..255) um eins
hochgezählt
u8T0_Runs++;
jetzt weiß man also immer , wo der Timer gerade ist.!
c) gebraucht wird nun ein
Werte-speicher( Array
etc ) der die PWM ON-Zeit für jeden PWM-Kanal
einzeln Speichert.
Jeder PWM
Kanal hat also sein eigene
Variable, die den Abschaltzeitpunkt ( fallende Flanke ) definiert.
!!
#define
PWM_CAHNNEL 7
uint8_t
gau8MultiPWM[ PWM_CHANNELS ];
// 7 PWMs. gau8= global array unsigned
8bit
Die Schrittweite des PWM Signal hat also NUR 255 Stufungen.
d) der Interrupt-Zähler u8T0_Runs
wird
nun mit der PWM-Variable des zuständigen PWM-Kanals
gau8MultiPWM[ PWMNummer ] verglichen.
Bei Gleichstand ist
der SollZeitpunkt erreicht
und die zugehörige PWM Signalleitung (Pin) wird schnell
auf 0
geschaltet
#define MULTI_SERVO_PORT
PORTB
if(
gau8MultiPWM[0] ==
u8T0_Runs) {MULTI_SERVO_PORT &=
0xFD};
if( gau8MultiPWM[1] == u8T0_Runs)
{MULTI_SERVO_PORT &=
0xFB;};
if( gau8MultiPWM[2] == u8T0_Runs)
{MULTI_SERVO_PORT &=
0xF7;};
usw...
Das
zugehörige Bit wird gelöscht. Und zwar
ohne die anderen Bits zu beeinflussen..
Das geht am besten mit VerUNDen
mit 0 des Portwertes an der Position, wo das zu
löschende Bit 0
ist.
0xFD = 0b11111101
0xFB = 0b11111011
0xF7 = 0b11110111
usw....
FERTIG !!!Die
Aufgabe. Mehrere präzise, jitterarme ( stabile)
PWMs für Servos:
Eine präzise Zeitbasis ist erforderlich. Und das ganze eben
sehr sehr
schnell.
Realisation:
1.) 16Mhz ohne Vorteilung in
einen freien 8 Bit Timer
des µControllers schieben, und damit Overflow Interrupts
auslösen...
merke: 8 Bit = 2^8
=256.
Der Zähler
läuft einfach von 0 bis 255,
und wenn er wieder 0
wird, löst er einen Overflow Interrupt aus.
Simpel
und präzise !
Das hat eine Frequenzteilung zu Folge.
16 MHZ / 256 = 62500
Interrupts / Sekunde !!
Software:
#define TCCR2_CLK_0
0x01
#define TIMSK_TOIE0_FLAG 0x01
TCCR0 = TCCR2_CLK_0;
CLOCK ON. Quarz_Clock
Direkt ohne Vorteilung an 8Bit Timer 0 anlegen
TIMSK |=
TIMSK_TOIE0_FLAG;
// Interrupt EIN
Pro Interrupt stehen also NUR 1/62500
= 0,000016 Sekunden = 16 ^-6
= 16µSekunden Zeit für die Verarbeitung
zur Verfügung.
Das ist nicht viel Zeit. Also geht (diesmal)
Zeitoptimierung vor Eleganz
und Schönheit des Programmierstils.
2.) Was
passiert im Interrupt:
Daraus ergibt
sich als Konsequenz für die PWM Frequenz eine weitere
Teilung durch 256 der Quarz-Frequenz
von z.B.16Mhz. ....Warum?..
Da
der Interrupt-Zähler u8T0_Runs
ebenfalls
von 0 biss 255 zählt, und eben erst bei jeden NULL-Wert
das PWM Signal
wieder eingeschaltet wird, ergibt sich eine PWM Ausgabe-Frequenz von
16 MHz(Quarz) / 256
(Timeroverflow) / 256 (
Interrupt Zähler) = 244,140625 Hz... PWM
Frequenz..
Damit ist soweit alles OK.. Es funktioniert..
Prima. Ende soweit... oder?
Schaltung
der MultiPWM Software. Mit einem Trick wird eine konstant
eSpannung bei hohem Strom erreicht. Zudem können alle Servos definiert
eingeschaltet werden.-
Die
Eagle Dateien zum Download: 7Servo_mitUSw.sch
7Servo_mitUSw.brd
Nur 244
Hz sind
z.B. für Servo Steuersignale zu schnell..... Die wollen 50..60Hz ...
kein Problem,,,,!!
jetzt kann man selbst noch erweitern, ergänzen, verändern, anpassen
etc..
~244 / 5 sind ja etwa 50 Hz
Am besten verlängert man die OFFZEIT einfach durch
Pausieren um den
entsprechenden Faktor/Betrag..
Also wird die Sequenz des Interrupt Vergleichs nur bei jedem
Fünften
Timer-Overflows wiederholt.
ein zweiter Zähler ist erforderlich..
static
uint8_t u8PWMPause
und der wird in der Software etwa so behandelt:
#define PWM_PAUSEVERLAENGUNGSFAKTOR 5
u8PWMPause %= PWM_PAUSEVERLAENGUNGSFAKTOR
// Dies ist der Modulo
Operator
u8PWMPause wird also mit Modulo Fünf
behandel.
Wenn also u8PWMPause wieder 0
wird, dann wird die PWM
wieder eingeschaltet und unterliegt wieder
dem Wertvergleich zum Ausschalten.
Mann kann jetzt also auch die Häufigkeit der PWM ON Signale erhöhen,
und so die
Leistung eines Servos erhöhen...
Dazu kann per Software eben diese Pausen-Zeit von 5
eben auch
auf
4
oder 3
etc
verringert werden...
Es erhöht sich also die PWM Frequenz, jedoch nicht die PWM ON
Zeit.
es folgt noch die HEADER und die .c Datei
Im
folgendem Beispiel ist die PWM Steuerung noch erweitert zur langsamen
Sollwertannäherung für Servos
und auch zur Leistungserhöhung, in dem die Pausenzeit verkürzt wird.
Siehe dazu die Text Mnemonic Commandos die mit MCA_XX_CMD
syntaktisch geschrieben sind
MulitPWM.h
#ifndef MULTI_PWM_HEADER //1.
Headereinbindung
#define MULTI_PWM_HEADER
#include <inttypes.h>
//" Grosse CPU % Auslastung! ~80%
#define MULTIPWM_USED // Interrupt getrieben.
//Diese Werte selbst definieren nach Bedarf
//BSP Chanel 0..6
#define PREDEF_PWM_0 75
#define PREDEF_PWM_1 75
#define PREDEF_PWM_2 75
#define PREDEF_PWM_3 75
#define PREDEF_PWM_4 75
#define PREDEF_PWM_5 75
#define PREDEF_PWM_6 75
//*******************************
//MULTI PWM
//*******************************
#define
PWM_CHANNELS
7 //sieben
PWMS
#define PWM_PAUSEVERLAENGUNGSFAKTOR 5
#define
PWRPIN_OFFSET
1 //Offset der PWMPins vom PWR_ON PIN
#define MULTI_SERVO_PORT
PORTB
#define MULTI_SERVO_DIRREG
DDRB
#define MULTI_SERVO_DIRREG_VAL 0xFF
#define MULTI_SERVO_DIRREG_Init() (MULTI_SERVO_DIRREG =
MULTI_SERVO_DIRREG_VAL)
#define MULTI_SERVO_PWR
0x01
//BUZ11
#define MULTI_SERVO_1
0x02
#define MULTI_SERVO_2
0x04
#define MULTI_SERVO_3
0x08
#define MULTI_SERVO_4
0x10
#define MULTI_SERVO_5
0x20
#define MULTI_SERVO_6
0x40
#define MULTI_SERVO_7
0x80
#define MULTI_SERVOS_MASK_1
0xFE
#define MULTI_SERVOS_CLRMASK 0x01 //
wegen PWR
/*
1mS=1000Hz * 180
*/
#define
PWM_PERIODE_MS
0.005 //20ms =
50HZ 5mS=200Hz
#define PWM_LINKS_ANSCHLAG_MS
1
#define PWM_RECHTS_SANSCHLAG_MS
2
#define MULTI_SERVO_PWR_ON() (MULTI_SERVO_PORT |=
MULTI_SERVO_PWR)
#define MULTI_SERVO_PWR_OFF() (MULTI_SERVO_PORT &=
~MULTI_SERVO_PWR)
#define MULTI_SERVO_1_ON() (MULTI_SERVO_PORT |= MULTI_SERVO_1)
#define MULTI_SERVO_1_OFF() (MULTI_SERVO_PORT &=
~MULTI_SERVO_1)
#define MULTI_SERVO_2_ON() (MULTI_SERVO_PORT |= MULTI_SERVO_2)
#define MULTI_SERVO_2_OFF() (MULTI_SERVO_PORT &=
~MULTI_SERVO_2)
#define MULTI_SERVO_3_ON() (MULTI_SERVO_PORT |= MULTI_SERVO_3)
#define MULTI_SERVO_3_OFF() (MULTI_SERVO_PORT &=
~MULTI_SERVO_3)
#define MULTI_SERVO_41_ON() (MULTI_SERVO_PORT |=
MULTI_SERVO_4)
#define MULTI_SERVO_4_OFF() (MULTI_SERVO_PORT &=
~MULTI_SERVO_4)
#define MULTI_SERVO_5_ON() (MULTI_SERVO_PORT |= MULTI_SERVO_5)
#define MULTI_SERVO_5_OFF() (MULTI_SERVO_PORT &=
~MULTI_SERVO_5)
#define MULTI_SERVO_6_ON() (MULTI_SERVO_PORT |= MULTI_SERVO_6)
#define MULTI_SERVO_6_OFF() (MULTI_SERVO_PORT &=
~MULTI_SERVO_6)
#define MULTI_SERVO_7_ON() (MULTI_SERVO_PORT |= MULTI_SERVO_7)
#define MULTI_SERVO_7_OFF() (MULTI_SERVO_PORT &=
~MULTI_SERVO_7)
//Diese Funktion VOR!! Mainloop einfügen
extern void ToggleTEST_BIT(uint8_t ucCnt);
extern uint8_t gu8PausenFaktor;
extern void Init_MultiPWM_StartValues(uint32_t u32Time);
extern void StartTimer0(void);
//Diese Funktion IN!! Mainloop einfügen
extern void SlowMotion_PWM_Check( uint32_t u32Time, uint16_t
u16PWMDelay);
//Extern Globals
extern uint8_t gau8MultiPWM[ PWM_CHANNELS ]; // 7 PWMs
extern uint8_t gau8MultiPWM_Compare[ PWM_CHANNELS ]; // 7 PWMs
//gu16PWM_DT = PWM_STEIGUNG ;
#define PWM_STEIGUNG 10; // STEIGUNG = Fahrtgeschwindigkeit
des PWM, 244 = 1Sekunde pro Schritt
#endif //MULTI_PWM_HEADER
MultiPWM.c
/*
Insert in MainPrgramm bevor Mainloop:
#ifdef MULTIPWM_USED
Init_MultiPWM_StartValues();
StartTimer0();
MULTI_SERVO_PWR_ON();
#endif
*/
#include <avr/io.h>
#include <avr\wdt.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include "main.h"
#include "MultiPwm.h"
#ifdef MULTIPWM_USED
//GLOBALS
uint8_t gau8MultiPWM[ PWM_CHANNELS ]; // 7 PWMs
uint8_t gau8MultiPWM_Compare[ PWM_CHANNELS ]; // 7 PWMs
static uint32_t gu32_PWM_MotionTime;
uint8_t gu8PausenFaktor = PWM_PAUSEVERLAENGUNGSFAKTOR;
void StartTimer0(void); //0.1
Seconds interrupt
void SlowMotion_PWM_Check( uint32_t u32Time, uint16_t u16PWMDelay);
#endif
// In Menmonic Orderliste ist folgendes hilfreich
// aber das je nach Geschmack des Programmierers
/*
#ifdef MULTIPWM_USED
//u8aMultiPWM[ (((uint8_t)gsCmd.fCmdVal_1) %
PWM_CHANNELS_MODULO) ] = (uint8_t)gsCmd.fCmdVal_2;
const char MCA_MPWM_CMD[]PROGMEM = "MPWM"; //MPWM, CHANNEL
0..6, 0..255
const char MCA_MPON_CMD[]PROGMEM = "MPON"; //MPWM, POWER ON
const char MCA_MPOFF_CMD[]PROGMEM = "MPOFF"; //MPWM, POWER OFF
#endif
#ifdef MULTIPWM_USED
if( !strcasecmp_P( gcaStr, MCA_MPWM_CMD) )
{
gu8aMultiPWM[
(((uint8_t)gsCmd.fCmdVal_1) % PWM_CHANNELS_MODULO) ] =
(uint8_t)gsCmd.fCmdVal_2;
return;
};
if( !strcasecmp_P( gcaStr, MCA_MPON_CMD) )
{
MULTI_SERVO_PWR_ON();
return;
};
if( !strcasecmp_P( gcaStr, MCA_MPOFF_CMD) )
{
MULTI_SERVO_PWR_OFF();
return;
};
#endif
*/
#ifdef MULTIPWM_USED
/*
^------|
^-- PWM
|
|
|
|
|---------|
0--------------->255
|||||||||||||||||| ---> Interrupt u8T0_Runs++
*/
// ****************************************************************
SIGNAL( TIMER0_OVF_vect ) //TIMER0_COMP_vect oder
SIGNAL--INTERRUPT(SIG_OVERFLOW0) = mit SEI am Anfang
// *******************************************************************
{ // 62500 Hz interrupt
/*
TCNT0=0 --> 16Mhz / 256 = 62500´Interrupts /Sekunde !!
Alle 20mS = 50Hz wir ein neuer PWM AUF H-pegel gelegt.
Timer läuf ohne Vorteiler am Quarz
Davon wird wieder ein
*/
static uint8_t u8PWMPause;
static uint8_t u8T0_Runs;
//uint8_t u8PWMs;
// weil die Peridoe 4.096 MS beträgt, jeoch 20Ms=50Hz erforderlich..
// TRick: uu8T0_Runs
//ToggleTEST_BIT(1);
//Diese Construction, damit nur alle 5 Timersdurchläufe der PWM
ausgegeben wird
if( !(u8T0_Runs++) ) // 5 * bei 20mS. WENN 0, Transportiert /Das
passiert 244,140625/Sekunde
{ // BEI
!0
++u8PWMPause;
//u8PWMPause
%=PWM_PAUSEVERLAENGUNGSFAKTOR;
if( !(u8PWMPause %= gu8PausenFaktor) )
{
MULTI_SERVO_PORT |= MULTI_SERVOS_MASK_1; //Alles ein
}
};
if(!u8PWMPause) //WENN 00 Modulo 5
{
//SO ist es das schnellste ausführungsvariante
if( gau8MultiPWM[0] ==
u8T0_Runs) {MULTI_SERVO_PORT &=
0xFD;};
if( gau8MultiPWM[1] ==
u8T0_Runs) {MULTI_SERVO_PORT &=
0xFB;};
if( gau8MultiPWM[2] ==
u8T0_Runs) {MULTI_SERVO_PORT &=
0xF7;};
if( gau8MultiPWM[3] ==
u8T0_Runs) {MULTI_SERVO_PORT &=
0xEF;};
if( gau8MultiPWM[4] ==
u8T0_Runs) {MULTI_SERVO_PORT &=
0xDF;};
if( gau8MultiPWM[5] ==
u8T0_Runs) {MULTI_SERVO_PORT &=
0xBF;};
if( gau8MultiPWM[6] ==
u8T0_Runs) {MULTI_SERVO_PORT &=
0x7F;};
};
//Spätes Interrupt freigeben, sonst pfuscht alles mögliche rein
sei();
/*
else
{
MULTI_SERVO_PORT &= MULTI_SERVOS_CLRMASK;
};
*/
/*
//Diese Form brauch Doppelt so lang, ist aber universel
//MCA_PWM_Clear_MASK[] = {0xFD,0xFB,0xF7,0xEF,0xDF,0xBF,0x7f};
//Remember , an BIT0 liegt der Transistor, der die Spannung auf Masse
freischaltet.
for(u8PWMs= 0; u8PWMs <
PWM_CHANNELS; u8PWMs++)
{
if(
gau8MultiPWM[u8PWMs] == u8T0_Runs )
{
MULTI_SERVO_PORT &= ~(1
<< (u8PWMs + 1)); // Offset 1, weil 0 ist der
Powertransistor
};
};
};
*/
};
#endif
#ifdef MULTIPWM_USED
void StartTimer0(void)
// *************************************
{
unsigned char sreg;
gu8PausenFaktor = PWM_PAUSEVERLAENGUNGSFAKTOR; //
Sondereuinstellung... um den Ablauf zu garantieren
cli();
sreg = SREG; //loakaler Speicher
TIMSK &= ~(TIMSK_TOIE0_FLAG | TIMSK_OCIE0_FLAG);
OCR0 = TCCR0 = TCNT0 = 0; //Timer MAX =
// CTC löst OCRx int aus
TCCR0 = TCCR2_CLK_0; // TCCR0_CLK_0; //
TIMSK |= TIMSK_TOIE0_FLAG;// TIMSK_OCIE0_FLAG ;
SREG = sreg;
sei();
};
#endif
#ifdef MULTIPWM_USED
// *************************************
void Init_MultiPWM_StartValues(uint32_t u32Time)
// *************************************
{
gu32_PWM_MotionTime = u32Time;
*gau8MultiPWM_Compare = *gau8MultiPWM = PREDEF_PWM_0; //gau8MultiPWM[0]
= PREDEF_PWM_0;
gau8MultiPWM_Compare[1] = gau8MultiPWM[1] = PREDEF_PWM_1;
gau8MultiPWM_Compare[2] = gau8MultiPWM[2] = PREDEF_PWM_2;
gau8MultiPWM_Compare[3] = gau8MultiPWM[3] = PREDEF_PWM_3;
gau8MultiPWM_Compare[4] = gau8MultiPWM[4] = PREDEF_PWM_4;
gau8MultiPWM_Compare[5] = gau8MultiPWM[5] = PREDEF_PWM_5;
gau8MultiPWM_Compare[6] = gau8MultiPWM[6] = PREDEF_PWM_6;
MULTI_SERVO_DIRREG_Init();
};
#endif
#ifdef MULTIPWM_USED
// *************************************
void SlowMotion_PWM_Check( uint32_t u32Time, uint16_t u16PWMDelay)
// *************************************
{
if( (u32Time - gu32_PWM_MotionTime ) > u16PWMDelay )
{
gu32_PWM_MotionTime = u32Time;
uint8_t u8NN;
for(u8NN=0;u8NN <
PWM_CHANNELS; u8NN++)
{
if(
gau8MultiPWM_Compare[u8NN] != gau8MultiPWM[u8NN] )
{
//
ToggleTEST_BIT(1);
(
gau8MultiPWM_Compare[u8NN] >
gau8MultiPWM[u8NN] ? gau8MultiPWM[u8NN]++ : gau8MultiPWM[u8NN]-- );
};
};
};
};
#endif
in main.c
BEISPIELHAFT
gsCmd.fCmdVal_1 = PREDEF_PWM_0;
gsCmd.fCmdVal_2 = PREDEF_PWM_1;
gsCmd.fCmdVal_3 = PREDEF_PWM_2;
gsCmd.fCmdVal_4 = PREDEF_PWM_3;
gsCmd.fCmdVal_5 = PREDEF_PWM_4;
gsCmd.fCmdVal_6 = PREDEF_PWM_5;
gsCmd.fCmdVal_7 = PREDEF_PWM_6;
Init_MultiPWM_StartValues(gu32T2_Ticks);
MULTI_SERVO_PWR_ON();
//in mainloop
while(1) /* loop forever */
{
sei();
wdt_reset(); //WATCHDOG!
SlowMotion_PWM_Check( gu32T2_Ticks,
gu16PWM_DT ); // 100 ist motion time
#ifdef ADC_POTI_CHECK
if( (gu16Last_ADC_VAL[0] - ADC_10Bit( 0
) > ADC_POTI_CHECK_DETECTDIFF ) )
{
gu16Last_ADC_VAL[0] = ADC_10Bit( 0 );
PrintiLongCR( gu16Last_ADC_VAL[0] );
};
#endif
u32MainCountsPerSecond++;
if( ((uint32_t)(gu32T2_Ticks - u32Now))
> PULS_PER_SEcONDTick_T2 ) //PULS_250MS_TICK_T2
{
u32Now =
gu32T2_Ticks;
PrintiLongCR( u32MainCountsPerSecond );
u32MainCountsPerSecond = 0;
};