DDS-Generator mit CH32V003          


Elektronik-Labor  Projekte  CH32V



Für einen DDS-Generator braucht man einen Timer-Interrupt als stabilen Ausgabetakt. Ein passendes Beispiel habe ich inzwischen gefunden: TIM_Int verwendet den Timer1. In dieses Beispielprogramm habe ich alle weiteren Teile eingefügt. Der Interrupt wird nun nach jeweils 80 Systemtakt-Impulsen wieder aufgerufen, also mit einer Frequenz von 600 kHz.

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;  
        TIM2->CH1CVR = sin_table[akk >> 24];
        GPIO_WriteBit(GPIOD, GPIO_Pin_2, akk >> 31);
    }
    TIM_ClearITPendingBit( TIM1, TIM_IT_Update );
}

Das Programm verwendet den 32-Bit Akku akk und fügt regelmäßig einen konstanten Betrag hinzu. Nur die oberen 8 Bits werden als Zeiger in die Sinustabelle für die Ausgabe an den PWM-Generator verwendet. Das Sinus-modulierte PWM-Signal erscheint an PD4. Zusätzlich gibt es einen digitalen Ausgang PD2, über den ein Rechtecksignal gleicher Frequenz ausgegeben wird.

Für die PWM-Ausgabe hatte ich ja schon mal ein Beispiel, das aber ebenfalls Timer1 verwendete. Die Initialisierung konnte relativ leicht für den Timer2 umgeändert werden. Der PWM-Ausgang für diesen Timer liegt an PD4. Der Timer zählt jeweils bis 255, sodass eine 8-Bit-Ausgabe entsteht, Die PWM-Frequenz beträgt 48 MHz / 256 = 187,5 kHz.

Die Sinus-Tabelle wollte ich zuerst im Programm selbst berechnen und habe dazu math.h eingefügt. Es gab jedoch Probleme mit der Funktion sin(), die ich nicht lösen konnte. Deshalb habe ich eine Tabelle mit den nötigen Stützwerten extern mit VB6 berechnet und hier eingefügt.

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, };

Das Ziel war, dass eine Frequenz in Hz über das Terminal eingegeben werden kann, die dann als Sinus und als Rechteck ausgegeben wird. Die serielle Eingabe war ja schon gelöst und konnte hier wiederverwertet werden. Die Frequenz wird mit getint() empfangen und dann in die Phasensprünge umgerechnet. für 1 Hz wird ein Phasensprung von 14496 im Akku benötigt. Diesen Faktor habe ich durch Messungen der Ausgangsfrequenz ermittelt.

    while(1){
        freq = getint();
        phase = freq*14496;
    };

Das Ergebnis ist nun ein DDS-Generator von 1 Hz bis weit über 20 kHz. Die obere Grenze hängt vom verwendeten Tiefpassfilter ab. Das Signal ist sehr stabil und damit vielseitig verwendbar.

Aber einiges könnte doch noch verbessert werden. Die DDS-Update-Frequenz liegt deutlich über der PWM-Frequenz, was keinen Nutzen bringt. Besser wäre es, wenn beide gleich wären. Vielleicht kann sogar der PWM-Timer den Interrupt selbst auslösen. Und ich würde gern den internen Operationsverstärker des Chips für das Tiefpassfilter verwenden. Das geht aber jetzt nicht, weil der Ausgang des OPV ebenfalls an PD4 liegt.



/********************************** (C) COPYRIGHT *******************************
 * File Name          : main.c
 * Author             : WCH
 * Version            : V1.0.0
 * Date               : 2024/08/23
 * Description        : CH32V003 Device Peripheral Access Layer System Header File.
*********************************************************************************
* Copyright (c) 2021 Nanjing Qinheng Microelectronics Co., Ltd.
* Attention: This software (modified or not) and binary are used for
* microcontroller manufactured by Nanjing Qinheng Microelectronics.
*******************************************************************************/

/*
 *DDS
 *Timer 1 als Timer-Interrupt
 *Timer2 für PWM-Ausgabe an D2
 */

#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, };

/*********************************************************************
 * @fn      TIM1_UP_IRQHandler
 *
 * @brief   This function handles TIM1 UP exception.
 *
 *
 * @return  none
 */
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;  
        TIM2->CH1CVR = sin_table[akk >> 24];
        GPIO_WriteBit(GPIOD, GPIO_Pin_2, akk >> 31);
    }
    TIM_ClearITPendingBit( TIM1, TIM_IT_Update );
}

/*********************************************************************
 * @fn      TIM1_INT_Init
 *
 * @brief   Initializes TIM1 output compare.
 *
 * @param   arr - the period value.
 *          psc - the prescaler value.
 *
 * @return  none
 */
void TIM1_INT_Init( u16 arr, u16 psc)
{

    NVIC_InitTypeDef NVIC_InitStructure={0};
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure={0};

    //RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE );
    RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOD | RCC_APB2Periph_TIM1 , ENABLE );

    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_TimeBaseInitStructure.TIM_RepetitionCounter = 80;
    TIM_TimeBaseInit( TIM1, &TIM_TimeBaseInitStructure);

    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 TIM2_PWMOut_Init(u16 arr, u16 psc, u16 ccp)
{
    GPIO_InitTypeDef GPIO_InitStructure={0};
    TIM_OCInitTypeDef TIM_OCInitStructure={0};
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure={0};

    RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOD | RCC_APB2Periph_TIM1 , ENABLE );
    RCC_APB1PeriphClockCmd( RCC_APB1Periph_TIM2, ENABLE );

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

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;  //D2 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( TIM2, &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_Low;
    TIM_OC1Init( TIM2, &TIM_OCInitStructure );

    TIM_CtrlPWMOutputs(TIM2, ENABLE );
    TIM_OC1PreloadConfig( TIM2, TIM_OCPreload_Disable );
    TIM_ARRPreloadConfig( TIM2, ENABLE );
    TIM_Cmd( TIM2, 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;
}


/*********************************************************************
 * @fn      main
 *
 * @brief   Main program.
 *
 * @return  none
 */
int main(void)
{
    SystemCoreClockUpdate();
    Delay_Init();

    USART_Printf_Init(115200);
    printf("SystemClk:%d\r\n",SystemCoreClock);
    printf( "ChipID:%08x\r\n", DBGMCU_GetCHIPID() );

    TIM1_INT_Init(2-1, 0);
    TIM_Cmd( TIM1, ENABLE );//5S
    TIM2_PWMOut_Init( 255, 0, 128 );
    USARTx_CFG();
    freq = 1000;
    phase = freq*14496;
    while(1){
        freq = getint();
        phase = freq*14496;
    };
}


Elektronik-Labor  Projekte  CH32V