CH32 Oscillator

From Stm32World Wiki
Jump to navigation Jump to search

In order to get to know the CH32V307 MCU better I decided to create a dual oscillator using the DACs.

Timer

A timer will be responsible for the sample frequency. The timer will be configured to run at 48000 Hz and it will generate an Update event for each clock.

void Timer4_Init (void) {
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure = { 0 };
    RCC_APB1PeriphClockCmd (RCC_APB1Periph_TIM4, ENABLE);

    TIM_TimeBaseStructInit (&TIM_TimeBaseStructure);
    TIM_TimeBaseStructure.TIM_Period = 3000 - 1;
    TIM_TimeBaseStructure.TIM_Prescaler = 0;
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit (TIM4, &TIM_TimeBaseStructure);

    TIM_SelectOutputTrigger (TIM4, TIM_TRGOSource_Update);
    TIM_Cmd (TIM4, ENABLE);
}

A Period of 3000 will give us our desired 48000 Hz (144000000 / 3000 = 48000).

DMA

Setting the values for the DAC 48000 times each second would require a lot of CPU overhead. It is much better to use DMA for this purpose. We allocate a buffer:

#define BUFFER_SIZE 48
uint32_t dac_buffer[2 * BUFFER_SIZE];

In this case we set the buffer size to 48 but we allocate a buffer twice that size so that we can update each half while the other half is being used b the DAC.

void Dac_Dma_Init (void) {
    DMA_InitTypeDef DMA_InitStructure = { 0 };
    RCC_AHBPeriphClockCmd (RCC_AHBPeriph_DMA2, ENABLE);

    DMA_StructInit (&DMA_InitStructure);
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) &(DAC->RD12BDHR);
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t) &dac_buffer[0];
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
    DMA_InitStructure.DMA_BufferSize = 2 * BUFFER_SIZE;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
    DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;

    DMA_Init (DMA2_Channel3, &DMA_InitStructure);

    DMA_ITConfig (DMA2_Channel3, DMA_IT_TC, ENABLE);
    DMA_ITConfig (DMA2_Channel3, DMA_IT_HT, ENABLE);

    DMA_Cmd (DMA2_Channel3, ENABLE);

}

As can be seen we allocate the buffer to the DMA as a circular buffer incrementing the address for each sample.

We also configure the DMA to generate an interrupt at half time (DMA_IT_HT) and transfer complete. That way we can update the first half of the buffer after we have received the half time interrupt and the second half of the buffer after the transfer is complete.

DAC

The CH32V307 has gone one DAC with two possible output channels. We'll run both of those channels simultaneously.

void Dual_Dac_Init (void) {
    GPIO_InitTypeDef GPIO_InitStructure = { 0 };
    DAC_InitTypeDef DAC_InitType = { 0 };

    // Make sure the APB busses are clocked
    RCC_APB2PeriphClockCmd (RCC_APB2Periph_GPIOA, ENABLE);
    RCC_APB1PeriphClockCmd (RCC_APB1Periph_DAC, ENABLE);

    // Configure PA4 and PA5 for analog output
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init (GPIOA, &GPIO_InitStructure);
    GPIO_SetBits (GPIOA, GPIO_Pin_4);

    // Throw a debug pulse out on PA6
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_Init (GPIOA, &GPIO_InitStructure);

    // DAC convertion triggered by Timer 4
    DAC_InitType.DAC_Trigger = DAC_Trigger_T4_TRGO;
    DAC_InitType.DAC_WaveGeneration = DAC_WaveGeneration_None;
    DAC_InitType.DAC_LFSRUnmask_TriangleAmplitude = DAC_LFSRUnmask_Bit0;
    DAC_InitType.DAC_OutputBuffer = DAC_OutputBuffer_Enable;
    DAC_Init (DAC_Channel_1, &DAC_InitType);
    DAC_Init (DAC_Channel_2, &DAC_InitType);

    DAC_Cmd (DAC_Channel_1, ENABLE);
    DAC_Cmd (DAC_Channel_2, ENABLE);

    DAC_DMACmd (DAC_Channel_1, ENABLE);
    DAC_DMACmd (DAC_Channel_2, ENABLE);

    DAC_SetDualChannelData (DAC_Align_12b_R, 0x123, 0x321);
}

First, the GPIOs are initialized. The conversion trigger will be the event from Timer4.

DMA Interrupt

The final thing that need to be enabled is the Interrupt from the DMA.

void DMA_Interrupt_Init () {
    /*Configuration interrupt priority*/
    NVIC_InitTypeDef NVIC_InitStructure = { 0 };
    NVIC_InitStructure.NVIC_IRQChannel = DMA2_Channel3_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //Seeing priority
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //Response priority
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //Enable
    NVIC_Init (&NVIC_InitStructure);
}

Oscillator Calculation

__attribute__((interrupt("WCH-Interrupt-fast"))) void DMA2_Channel3_IRQHandler () {

    // To time the ISR throw debug out hi
    GPIO_WriteBit (GPIOA, GPIO_Pin_6, Bit_SET);

    if (DMA_GetITStatus (DMA2_IT_TC3) != RESET) {
        ++full_count;
        update_dac_buffer (&dac_buffer[BUFFER_SIZE]);
        DMA_ClearITPendingBit (DMA2_IT_TC3);
    }
    else if (DMA_GetITStatus (DMA2_IT_HT3) != RESET) {
        ++half_count;
        update_dac_buffer (&dac_buffer[0]);
        DMA_ClearITPendingBit (DMA2_IT_HT3);
    }

    // Finally toggle debug out low again
    GPIO_WriteBit (GPIOA, GPIO_Pin_6, Bit_RESET);

}
static inline void update_dac_buffer (uint32_t *buffer_address) {
    for (uint8_t sample = 0; sample < BUFFER_SIZE; ++sample) {
        for (uint8_t oscillator = 0; oscillator < 2; ++oscillator) {
            osc[oscillator].last_value = osc[oscillator].amplitude * sinf (osc[oscillator].angle);
            osc[oscillator].angle += osc[oscillator].angle_per_sample; // rotate
            if (osc[oscillator].angle > M_TWOPI)
                osc[oscillator].angle -= M_TWOPI; // roll over
        }
        buffer_address[sample] = (((uint16_t) (MID_POINT + MID_POINT * osc[1].last_value)) << 16) | ((uint16_t) (MID_POINT + MID_POINT * osc[0].last_value));
    }
}

Miscellaneous Links