CH32V003 Frequenzzähler          


Elektronik-Labor  Projekte  CH32V


Für einen einfachen Frequenzzähler bis 65 kHz verwende ich den Timer1 mit dem externen Input am Pin PD2. Da es sich um einen 16-Bit-Zähler handelt, kann ich in einer Sekunde nur ca. 65000 Impulse zählen. Als Zeitbasis dient der Systick-Zähler, der periodisch einen Interrupt aufruft. In der Interruptfunktion wird der Timer1 ausgelesen und zurückgesetzt. Das Ergebnis wird dann an den PC gesendet. Am Ende wird auch der Sys-Timer auf null gesetzt, damit der nächste Aufruf genau eine Sekunde später kommt.

/*
 Freq5 bis 65 kHz
 Systick 1s
*/

#include "debug.h"

volatile uint32_t interrupt_count = 0;
volatile uint32_t pulse_count;

void TIM1_ETRClockMode1_Init(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1 | RCC_APB2Periph_GPIOD, ENABLE);
    TIM_CounterModeConfig(TIM1, TIM_CounterMode_Up);
    TIM_SetAutoreload(TIM1, 0xFFFF);
    TIM_ETRClockMode1Config(TIM1, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_Inverted, 0x0);
    /* GPIOD2 Input as TIM Clock Source */
    TIM_TIxExternalClockConfig(TIM1, TIM_TIxExternalCLK1Source_TI1, TIM_ICPolarity_Rising, 0x00);
    TIM_Cmd(TIM1, ENABLE);
}

int main(void)
{
    SystemCoreClockUpdate();
    Delay_Init();
    USART_Printf_Init(115200);
    TIM1_ETRClockMode1_Init();
    NVIC_EnableIRQ(SysTick_IRQn);
    SysTick->SR &= ~(1 << 0);
    SysTick->CMP = SystemCoreClock-1; //Sekunde
    SysTick->CNT = 0;
    SysTick->CTLR = 0xF;
    while(1);
}

void SysTick_Handler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
void SysTick_Handler(void)
{
    pulse_count = TIM1->CNT;
    TIM1->CNT=0;
    printf("%d\r\n", pulse_count);
    SysTick->SR = 0;
}


Für höhere Frequenzen muss man die Timer-Überläufe berücksichtigen. Zuerst habe ich versucht, einen Interrupt dazu zu programmieren. Das ist mir jedoch nicht gelungen, und auch die KI konnte mir nicht helfen. Dann habe ich die Überläufe "per Hand" ausgewertet, und zwar in der Systick-Interruptfunktion. Sie wird jetzt nicht mehr im Sekundentakt  aufgerufen, sondern jede Millisekunde einmal. Hier wird wieder der aktuelle Timer1-Zählerstand ausgelesen. Wenn er kleiner ist als bei der letzten Abfrage, gab es inzwischen einen Überlauf. Dann wird pulse_high erhöht. Theoretisch könnte man damit Ergebnisse his 32 Bit erfassen. Aber der Timer1 kann maximal 24 MHz zählen.

/*
 Freq6 bis 24 MHz
 Systick 1 ms
*/

#include "debug.h"

volatile uint32_t interrupt_count = 0;
volatile uint32_t pulse_count = 0;
volatile uint32_t pulse_last = 0;
volatile uint32_t pulse_high = 0;

void TIM1_ETRClockMode1_Init(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1 | RCC_APB2Periph_GPIOD, ENABLE);
    TIM_CounterModeConfig(TIM1, TIM_CounterMode_Up);
    TIM_SetAutoreload(TIM1, 0xFFFF);
    TIM_ETRClockMode1Config(TIM1, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_Inverted, 0x0);
    /* GPIOD2 Input as TIM Clock Source */
    TIM_TIxExternalClockConfig(TIM1, TIM_TIxExternalCLK1Source_TI1, TIM_ICPolarity_Rising, 0x00);
    TIM_Cmd(TIM1, ENABLE);
}

int main(void)
{
    SystemCoreClockUpdate();
    Delay_Init();
    USART_Printf_Init(115200);
    TIM1_ETRClockMode1_Init();
    NVIC_EnableIRQ(SysTick_IRQn);
    SysTick->SR &= ~(1 << 0);
    SysTick->CMP = SystemCoreClock/1000-1; //Millisekunde
    SysTick->CNT = 0;
    SysTick->CTLR = 0xF;
    while(1);
}

void SysTick_Handler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
void SysTick_Handler(void)
{
    pulse_count = TIM1->CNT;
    interrupt_count++;
    if (pulse_count < pulse_last) {pulse_high++;}
    pulse_last = pulse_count;
    if (interrupt_count==1000){
        printf("%d\r\n", (pulse_high << 16) | pulse_count);
        pulse_high = 0;
        pulse_last = 0;
        interrupt_count = 0;
        TIM1->CNT=0;
        SysTick->CNT = 0;
    }  
    SysTick->SR = 0;
}

Eigentlich war diese Methode der Überlauferfassung als Kompromiss gedacht. Sie scheint jedoch einen großen Vorteil zu haben. Es gibt nur eine Abfrage des Zählerstands, immer im genau gleichen Takt. Mit einem zweiten Interrupt könnte es passieren, dass das Endergebnis genau zum Zeitpunkt einen Überlaufs ausgelesen wird. Dann wäre unklar, welcher Interrupt zuerst ausgeführt wird, und damit gäbe es eine Unsicherheit  im Ergebnis. Das kann hier nicht passieren. Alles nach pulse_count = TIM1->CNT; darf auch etwas länger dauern, das Ergebnis kann sich dadurch nicht mehr ändern.



Hier sieht man das Signal mit ca. 20 MHz von einem analogen HF-Generator. Das Gerät hatte eine zu kleine Signalspannung, um noch wesentlich höhere Frequenzen zu messen. Die echte Grenze von 24 MHz ist damit noch nicht getestet..


Elektronik-Labor  Projekte  CH32V