Code:
// Enuberation für 1 bis 10000
enum incs
{
ones = 1,
tens = 10,
hundrets = 100,
thousends = 1000,
tenthousends = 10000
};
// Globale Variable für das aktuelle PER-Register
static volatile uint16_t current_per = 240;
uint8_t pwm_set_frequency(uint32_t freq_hz) {
// Sicherheitsgrenzen prüfen
if (freq_hz < 1 || freq_hz > 100000) {
return 0; // Fehler: Frequenz außerhalb des erlaubten Bereichs
}
uint32_t ticks = 0;
uint8_t clk_sel = 0;
// Timer stoppen für glitchfreies Umschalten
uint8_t ctrla_backup = TCA0.SINGLE.CTRLA;
TCA0.SINGLE.CTRLA &= ~TCA_SINGLE_ENABLE_bm;
// Dynamische Berechnung des optimalen Prescalers bei 24 MHz
if (freq_hz >= 367) {
ticks = F_CPU / freq_hz;
clk_sel = TCA_SINGLE_CLKSEL_DIV1_gc;
} else if (freq_hz >= 92) {
ticks = (F_CPU / 4) / freq_hz;
clk_sel = TCA_SINGLE_CLKSEL_DIV4_gc;
} else if (freq_hz >= 46) {
ticks = (F_CPU / 8) / freq_hz;
clk_sel = TCA_SINGLE_CLKSEL_DIV8_gc;
} else if (freq_hz >= 23) {
ticks = (F_CPU / 16) / freq_hz;
clk_sel = TCA_SINGLE_CLKSEL_DIV16_gc;
} else if (freq_hz >= 6) {
ticks = (F_CPU / 64) / freq_hz;
clk_sel = TCA_SINGLE_CLKSEL_DIV64_gc;
} else if (freq_hz >= 2) {
ticks = (F_CPU / 256) / freq_hz;
clk_sel = TCA_SINGLE_CLKSEL_DIV256_gc;
} else {
ticks = (F_CPU / 1024) / freq_hz;
clk_sel = TCA_SINGLE_CLKSEL_DIV1024_gc;
}
// Das Periodenregister schreiben
current_per = (uint16_t)(ticks - 1);
TCA0.SINGLE.PER = current_per;
// Neuen Prescaler in CTRLA schreiben
TCA0.SINGLE.CTRLA = (TCA0.SINGLE.CTRLA & ~TCA_SINGLE_CLKSEL_gm) | clk_sel;
// Timer wieder starten, falls er vorher lief
TCA0.SINGLE.CTRLA |= (ctrla_backup & TCA_SINGLE_ENABLE_bm);
return 1; // Erfolg
}
void pwm_set_duty_cycle_percent(float percent) {
if (percent < 0.0) percent = 0.0;
if (percent > 100.0) percent = 100.0;
// Berechnung des Registerwertes basierend auf der aktuellen Periode
uint16_t cmp_value = (uint16_t)((percent / 100.0) * current_per);
// Vergleichsregister für Kanal 0 beschreiben (Gibt das Signal nun auf PA0 aus)
if(percent >= 100)cmp_value++;
TCA0.SINGLE.CMP0 = cmp_value;
}
void pwm_init(void) {
// 1. PORTMUX konfigurieren: TCA0-Ausgang WO0 von PORTA auf PORTC umleiten
// Das Bitfeld TCA0_bm in PORTMUX.TCAROUTEA steuert das Routing für TCA0
//PORTMUX.TCAROUTEA = PORTMUX_TCA0_PORTC_gc;
// 2. Pin PC0 (WO0) als Ausgang definieren
PORTA.DIRSET = PIN0_bm;
// 3. TCA0 im Single-Slope PWM Modus konfigurieren (WGMODE = SINGLESLOPE)
TCA0.SINGLE.CTRLB = TCA_SINGLE_WGMODE_SINGLESLOPE_gc | TCA_SINGLE_CMP0EN_bm;
// 4. Standardfrequenz beim Start setzen (z. B. 1 kHz)
pwm_set_frequency(1000);
pwm_set_duty_cycle_percent(50.0); // 50% Tastverhältnis starten
// 5. Timer aktivieren (Der Prescaler wird in pwm_set_frequency gesetzt)
TCA0.SINGLE.CTRLA |= TCA_SINGLE_ENABLE_bm;
}
Das funktioniert natürlich nur bei hohen Teilerfaktoren also niedrigen Frequenzen.
Beim kleinsten Teilerfaktor = 1 sind dann schon 425Hz Sprünge drin.
Das geht im Prinzip auch nicht genauer, weil der Controller natürlich nur Ganze Zahlen für das MAX ( PER ) Register verwenden kann.
Das ist also ein Fehler von 0,4%.
Bei größeren Teilfaktoren wird natürlich die Frequenzauflösung besser bis hinunter in den Hz Bereich.
120kHz wären bei Duty Cycle mit 0,5% auch noch möglich. Aber auch der L293D schafft das nicht.
Es läuft der interne 24MHz Generator. An Peripherie ist nur der Treiber, das Display, der Drehencoder und das Poti. Betrieben wird das Ganze vorerst mit nem Labornetzteil.
Lesezeichen