PWM-Kanäle und Remapping         


Elektronik-Labor  Projekte  CH32V


Könnte ich mit den CH32V003 ein System mit PicoBasic und TestLab bauen, wie es das für den Nano, Pico und Tiny3216 schon gibt? Gebraucht werden ein vollständiger 8-Bit-Port, zwei PWM-Ausgänge und drei AD-Eingänge. Als vollständiger Port kommt nur der Port C in Betracht. Daneben liegen D2 und D3, die ich als PWM verwenden könnte. D5 und D6 sind für RX und TX reserviert. A1, A2 und D4 können als AD-Eingänge verwendet werden. Damit hätte man als besondere Zugabe den internen OPV mit den Eingängen an D0 und D7. Er könnte als Verstärker für den linken AD-Eingang eingesetzt werden. Die Aufteilung könnte nicht nur für das TestLab, sondern auch für viele andere Anwendungen nützlich werden.

Aber zuerst muss einmal geklärt werden, wie ich alle Anschlüsse in dieser Art ansteuern kann. Eine Lösung für schnelle Portzugriffe und Änderungen der Konfiguration ist ja schon gefunden. AD-Eingänge wurden auch schon verwendet, der OPV ebenfalls. Auch die PWM-Eingänge waren schon im Einsatz, aber noch nicht an D3. Man kann Timer1 oder Timer2 verwenden. Jeder hat vier mögliche PWM-Ausgänge. Mein Wunsch war es, zwei Kanäle am selben Timer einzusetzen. Ein Blick ins Datenblatt zeigt die Möglichkeiten für D2 und D3.



Jeder Anschluss hat eine Grundfunktion als Port und eine alternative Funktion für die Peripherie. Dazu gibt es das Remapping in drei Stufen, womit z.B. andere Kanäle eines Timers an den Pin gelegt werden. Die Zusätze _1, _2 und _3 bezeichnen die Remapping-Stufe. Na gut dachte ich, dann nehme ich T1CH1 an PD2 und setze T1CH4 per Remapping an PD3. Wie das im Prinzip geht, habe ich auch herausgefunden. Hier die drei Stufen, wobei FullRemap für die Stufe 3 steht.

    GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM1, ENABLE);
    GPIO_PinRemapConfig(GPIO_PartialRemap2_TIM1, ENABLE);
    GPIO_PinRemapConfig(GPIO_FullRemap_TIM1, ENABLE);

Aber leider führte das nicht zum Erfolg. Was immer ich versucht habe, es war nicht möglich zwei Kanäle desselben Timers auf D2 und D3 zu schalten. Das liegt daran, dass das Remapping immer gleich mehrere oder alle Ausgänge der Peripherie betrifft. Ein neuer Blick ins Datenblatt brachte die Einsicht, dass es nur mit beiden Timern geht, was ich eigentlich vermeiden wollte. Ganz ohne Remapping liegt T1CH1 an PD2 und T2CH2 an PD3.

Vermutlich ist das zu verkraften, denn ich habe ja noch den SysTimer  für die Zeitmessung. Und mit zwei getrennten Timern für die PWM-Ausgabe tun  sich ganz neue Möglichkeiten auf. Jeder hat seinen eigenen Vorteiler, sodass die PWM-Frequenz unabhängig eingestellt werden könnte. Auf die Weise hätte man zugleich zwei einstellbare Rechteckgeneratoren.

Als Übung für diesen Einsatz der beiden Timer habe ich den ursprünglichen DDS-Generator ohne OPV auf zwei Kanäle erweitert. Der zweite Kanal lief zuerst ebenfalls als Sinusgenerator, wurde dann aber als Rechteckgenerator mit dem Pulsverhältnis 50% und verändertem Vorteiler betrieben. Weil der Vorteiler selbst eine Breite von 16 Bit hat, kommt man herunter bis ca. 3 Hz. Das würde sogar reichen, um ein PWM-Signal mit einer LED anschaulich sichtbar zu machen.


/********************************** (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 und PWM an D2
 *Timer2 für PWM-Ausgabe an D3
 Vorteil: Getrennte Frequenzen m?glich
 Rechteck an D4
 */

#include "debug.h"
#include "math.h"
u32 freq, akk, phase;
//u16 n;

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;  
        TIM1->CH1CVR = sin_table[akk >> 24];
        TIM2->CH2CVR = 128;  // sin_table[akk >> 24];
        GPIO_WriteBit(GPIOD, GPIO_Pin_4, 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_2 | GPIO_Pin_3;
    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_4;
    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_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_Low;
    TIM_OC2Init( TIM2, &TIM_OCInitStructure );
    TIM_OC1Init( TIM1, &TIM_OCInitStructure );

    //GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE);

    TIM_CtrlPWMOutputs(TIM1, ENABLE );
    TIM_CtrlPWMOutputs(TIM2, ENABLE );
    TIM_OC1PreloadConfig( TIM1, TIM_OCPreload_Disable );
    TIM_OC1PreloadConfig( TIM2, TIM_OCPreload_Disable );
    TIM_ARRPreloadConfig( TIM1, ENABLE );
    TIM_ARRPreloadConfig( TIM2, ENABLE );
    TIM_Cmd( TIM1, 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(20-1, 0);
    TIM_Cmd( TIM1, ENABLE );//5S
    TIM2_PWMOut_Init( 255, 0, 128 );
    USARTx_CFG();
    freq = 1000;
    phase = freq*14496;

    TIM2->PSC= 65000;   //PWM-Freq ca. 3 Hz an D3

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



Elektronik-Labor  Projekte  CH32V