DDS-Generator mit internem OPV          


Elektronik-Labor  Projekte  CH32V





Um den internen OPV des CH30V003 für ein Tiefpassfilter nutzen zu können, musste ich zuerst einmal das PWM-Signal mit dem Timer1 erzeugen. Sein Ausgang liegt an PD2 und kommt dem OPV nicht in die Quere. Mein Wunsch war, dass der PWM-Timer zugleich auch den Interrupt auslöst. Das war einfacher als gedacht. Ich musste nur ein paar Zeilen zusätzlich in die Initialisierungs-Funktion kopieren:

    TIM_ClearITPendingBit( TIM1, TIM_IT_Update );

    NVIC_InitStructure.NVIC_IRQChannel =TIM1_UP_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority =0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority =1;
    NVIC_InitStructure.NVIC_IRQChannelCmd =ENABLE;
    NVIC_Init(&NVIC_InitStructure);

Jetzt laufen das PWM-Signal und die Interruptfunktion synchron mit 187,5 kHz. Daraus ergibt sich ein neuer Faktor für die Umrechnung der Frequenz in den übergebenen Phasensprung: 2^32 / 187500 Hz = 22907/Hz.

int main(void)
{
    SystemCoreClockUpdate();
    Delay_Init();
    TIM1_PWMOut_Init( 255, 0, 128 );
    Option_Byte_CFG;
    OPA1_Init();
    USARTx_CFG();
    freq = 1000;
    phase = freq*22907;
    while(1){
        freq = getint();
        phase = freq*22907;
    };
}

Außerdem muss der OPV initialisiert werden. Wie das geht, habe ich in einem Programmbeispiel zum OPV gefunden. Dort wurde auch klar, dass man den Controller-Resetpin abschalten muss, denn das ist PD7, der nun als invertierender Eingang des OPV dienen soll. Die Umschaltung läuft über eine Programmierung der OptionBytes. Das muss eigentlich nur einmal passieren, der Reset-Eingang bleibt dann dauerhaft abgeschaltet..

void Option_Byte_CFG(void)
{
    FLASH_Unlock();
    FLASH_EraseOptionBytes();
    FLASH_UserOptionByteConfig(OB_IWDG_SW, OB_STDBY_NoRST, OB_RST_NoEN ,OB_PowerON_Start_Mode_BOOT);
    FLASH_Lock();
}

void OPA1_Init( void )
{
    GPIO_InitTypeDef GPIO_InitStructure = {0};
    OPA_InitTypeDef  OPA_InitStructure = {0};

    RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOD, ENABLE );
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init( GPIOD, &GPIO_InitStructure );
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init( GPIOD, &GPIO_InitStructure );

    OPA_InitStructure.PSEL = CHP1;
    OPA_InitStructure.NSEL = CHN1;
    OPA_Init( &OPA_InitStructure );
    OPA_Cmd( ENABLE );
}




Das Tiefpassfilter konnte provisorisch aufgebaut werden, wobei ich alles auf passende Steckkontakte gelötet habe.  Die Dimensionierung richtete sich nach dem, was gerade da war. Ein einzelnes RC-Glied mit 1 k und 3,3 n hat eine Grenzfrequenz von 48 kHz. Die gesamte Schaltung sollte etwas tiefer liegen, aber wohl 20 kHz noch im Durchlassbereich behalten. Genauer habe ich es nicht berechnet oder simuliert. Es hat auch gut funktioniert, allerdings war die Grenzfrequenz deutlich tiefer. Nach längerer Suche habe ich die Ursache gefunden. Der Resettaster und ein keramischer Kondensator von 100 nF liegen immer noch am Pin D7. Der Taster stört nicht, aber der Kondensator verhagelt mir die Grenzfrequenz. Ich könnte ihn auslöten, aber eine bessere Lösung ist der Aufbau einer eigenen kleinen Platine.

// DDS2

#include "debug.h"

/*
 *DDS
 *Timer1 für PWM-Ausgabe an D4 Timer-Interrupt
 */

#include "debug.h"
u32 freq, akk, phase;

const uint8_t sin_table[256] =
{128,  131,  134,  137,  140,  143,  146,  149,  152,  156,  159,  162,  165,  168,  171,  173,  
176,  179,  182,  185,  188,  190,  193,  196,  199,  201,  204,  206,  209,  211,  213,  216,  
218,  220,  222,  224,  226,  228,  230,  232,  234,  235,  237,  239,  240,  242,  243,  244,  
246,  247,  248,  249,  250,  251,  251,  252,  253,  253,  254,  254,  255,  255,  255,  255,  
255,  255,  255,  255,  255,  254,  254,  253,  253,  252,  251,  251,  250,  249,  248,  247,  
246,  244,  243,  242,  240,  239,  237,  235,  234,  232,  230,  228,  226,  224,  222,  220,  
218,  216,  213,  211,  209,  206,  204,  201,  199,  196,  193,  191,  188,  185,  182,  179,  
176,  174,  171,  168,  165,  162,  159,  156,  152,  149,  146,  143,  140,  137,  134,  131,  
128,  124,  121,  118,  115,  112,  109,  106,  103,  100,  96,  93,  90,  87,  84,  82,  79,  
76,  73,  70,  67,  65,  62,  59,  57,  54,  51,  49,  46,  44,  42,  39,  37,  35,  33,  31,  
29,  27,  25,  23,  21,  20,  18,  16,  15,  13,  12,  11,  9,  8,  7,  6,  5,  4,  4,  3,  2,  
2,  1,  1,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  1,  2,  2,  3,  4,  4,  5,  6,  7,  8,  9,  
11,  12,  13,  15,  16,  18,  20,  21,  23,  25,  27,  29,  31,  33,  35,  37,  39,  42,  44,  
46,  49,  51,  54,  56,  59,  62,  64,  67,  70,  73,  76,  79,  81,  84,  87,  90,  93,  96,  
99,  103,  106,  109,  112,  115,  118,  121,  124, };

void TIM1_UP_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
void TIM1_UP_IRQHandler(void)
{
    if(TIM_GetITStatus(TIM1, TIM_IT_Update)==SET)
    {
        akk += phase;  
        TIM1->CH1CVR = sin_table[akk >> 24];
        GPIO_WriteBit(GPIOD, GPIO_Pin_3, akk >> 31);
    }
    TIM_ClearITPendingBit( TIM1, TIM_IT_Update );
}


void TIM1_PWMOut_Init(u16 arr, u16 psc, u16 ccp)
{
    GPIO_InitTypeDef GPIO_InitStructure={0};
    TIM_OCInitTypeDef TIM_OCInitStructure={0};
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure={0};
    NVIC_InitTypeDef NVIC_InitStructure={0};

    RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOD | RCC_APB2Periph_TIM1, ENABLE );

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //PWM, Sinus
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_30MHz;
    GPIO_Init( GPIOD, &GPIO_InitStructure );

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;  //D3 Rechteck
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_30MHz;
    GPIO_Init( GPIOD, &GPIO_InitStructure );

    TIM_TimeBaseInitStructure.TIM_Period = arr;
    TIM_TimeBaseInitStructure.TIM_Prescaler = psc;
    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit( TIM1, &TIM_TimeBaseInitStructure);

    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_Pulse = ccp;
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    TIM_OC1Init( TIM1, &TIM_OCInitStructure );

    TIM_CtrlPWMOutputs(TIM1, ENABLE );
    TIM_OC1PreloadConfig( TIM1, TIM_OCPreload_Disable );
    TIM_ARRPreloadConfig( TIM1, ENABLE );
    TIM_Cmd( TIM1, ENABLE );

    TIM_ClearITPendingBit( TIM1, TIM_IT_Update );

    NVIC_InitStructure.NVIC_IRQChannel =TIM1_UP_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority =0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority =1;
    NVIC_InitStructure.NVIC_IRQChannelCmd =ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    TIM_ITConfig(TIM1, TIM_IT_Update, ENABLE);
}

void Option_Byte_CFG(void)
{
    FLASH_Unlock();
    FLASH_EraseOptionBytes();
    FLASH_UserOptionByteConfig(OB_IWDG_SW, OB_STDBY_NoRST, OB_RST_NoEN ,OB_PowerON_Start_Mode_BOOT);
    FLASH_Lock();
}

void OPA1_Init( void )
{
    GPIO_InitTypeDef GPIO_InitStructure = {0};
    OPA_InitTypeDef  OPA_InitStructure = {0};

    RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOD, ENABLE );
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init( GPIOD, &GPIO_InitStructure );
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init( GPIOD, &GPIO_InitStructure );

    OPA_InitStructure.PSEL = CHP1;
    OPA_InitStructure.NSEL = CHN1;
    OPA_Init( &OPA_InitStructure );
    OPA_Cmd( ENABLE );
}

void USARTx_CFG(void)
{
    GPIO_InitTypeDef  GPIO_InitStructure = {0};
    USART_InitTypeDef USART_InitStructure = {0};

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD | RCC_APB2Periph_USART1, ENABLE);

    // USART1 TX-->D.5   RX-->D.6
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_30MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOD, &GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOD, &GPIO_InitStructure);

    USART_InitStructure.USART_BaudRate = 115200;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_Parity = USART_Parity_No;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;

    USART_Init(USART1, &USART_InitStructure);
    USART_Cmd(USART1, ENABLE);
}

int getchar(void) {
    while((USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET)){}      
    return (USART_ReceiveData(USART1));
}

uint16_t getint(void){
  char c;
  uint16_t n=0;
  while((USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET)){}      
  c=(USART_ReceiveData(USART1));
  while (c > 13){
    if (c>47 && c<58){
      n=n*10;
      n+=c-48;
    }
    while((USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET)){}      
    c=(USART_ReceiveData(USART1));
   }
   return n;
}

int main(void)
{
    SystemCoreClockUpdate();
    Delay_Init();
    TIM1_PWMOut_Init( 255, 0, 128 );
    Option_Byte_CFG;
    OPA1_Init();
    USARTx_CFG();
    freq = 1000;
    phase = freq*22907;
    while(1){
        freq = getint();
        phase = freq*22907;
    };
}




Am Ende habe ich einen CH32V003 auf eine SMD-Platine gelötet und alles noch einmal auf einer Steckplatine aufgebaut. Das Tiefpassfilter läuft nun wie geplant, bis 20 kHz gibt es kaum einen Abfall der Amplitude. Auch Sinussignale bis 40 kHz sind noch brauchbar.

Der Controller läuft jetzt ohne Quarz. Deshalb musste ich den internen RC-Oszillator einschalten. Die entscheidende Einstellung liegt in der Datei system_ch32v00x.c. Der Prozessortakt bleibt bei 48 MHz, ist aber jetzt nicht mehr ganz so genau.


//#define SYSCLK_FREQ_8MHz_HSI    8000000
//#define SYSCLK_FREQ_24MHZ_HSI   HSI_VALUE
#define SYSCLK_FREQ_48MHZ_HSI   48000000
//#define SYSCLK_FREQ_8MHz_HSE    8000000
//#define SYSCLK_FREQ_24MHz_HSE   HSE_VALUE
//#define SYSCLK_FREQ_48MHz_HSE   48000000


Elektronik-Labor  Projekte  CH32V