Difference between revisions of "CH32 Oscillator"
(5 intermediate revisions by the same user not shown) | |||
Line 5: | Line 5: | ||
== Timer == | == 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. | ||
<pre> | <pre> | ||
Line 22: | Line 24: | ||
} | } | ||
</pre> | </pre> | ||
+ | |||
+ | A Period of 3000 will give us our desired 48000 Hz (144000000 / 3000 = 48000). | ||
== DMA == | == 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: | ||
+ | |||
+ | <pre> | ||
+ | #define BUFFER_SIZE 48 | ||
+ | uint32_t dac_buffer[2 * BUFFER_SIZE]; | ||
+ | </pre> | ||
+ | |||
+ | 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. | ||
<pre> | <pre> | ||
Line 52: | Line 65: | ||
} | } | ||
</pre> | </pre> | ||
+ | |||
+ | 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 == | == DAC == | ||
+ | |||
+ | The [[CH32V307]] has gone one DAC with two possible output channels. We'll run both of those channels simultaneously. | ||
<pre> | <pre> | ||
Line 93: | Line 112: | ||
} | } | ||
</pre> | </pre> | ||
+ | |||
+ | First, the [[GPIO]]s are initialized. The conversion trigger will be the event from Timer4. | ||
== DMA Interrupt == | == DMA Interrupt == | ||
+ | |||
+ | The final thing that need to be enabled is the Interrupt from the DMA. | ||
<pre> | <pre> | ||
Line 109: | Line 132: | ||
== Oscillator Calculation == | == Oscillator Calculation == | ||
+ | |||
+ | By now, timer, dma and the dac is fully configured and will run off of the buffer defined. Unfortunately, the buffer is filled with zeros, so we need to fill some data into it. | ||
+ | |||
+ | The Interrupt handler will be called twice during the run of the full buffer. The full buffer got 96 elements, so the interrupt will be generated after 48 values have been transferred to the DAC, and then again after the full 96. | ||
<pre> | <pre> | ||
Line 132: | Line 159: | ||
} | } | ||
</pre> | </pre> | ||
+ | |||
+ | After the full transfer we can update the second half of the buffer and after the halfway point we can update the first. | ||
+ | |||
+ | The actual calculation becomes quite simple. Rather than calculating based on frequency the frequency is turned into an angular speed (in angular change per sample), so we simply add this value to the total value at each sample. We also make sure the angle never get above 2 * PI (full circle) by subtracting 2PI when it does get too large. | ||
<pre> | <pre> | ||
Line 146: | Line 177: | ||
} | } | ||
</pre> | </pre> | ||
+ | |||
+ | == More Videos == | ||
+ | |||
+ | Dual oscillator slightly out of sync (250 Hz and 249.9 Hz): | ||
+ | |||
+ | {{#ev:youtube|NCrP30HB1hI}} | ||
+ | |||
+ | X-Y plot: | ||
+ | |||
+ | {{#ev:youtube|hntzZT4-Wmk}} | ||
== Miscellaneous Links == | == Miscellaneous Links == | ||
* [https://github.com/lbthomsen/CH32V307V-EVT-R1/tree/master/oscillator Project on Github] | * [https://github.com/lbthomsen/CH32V307V-EVT-R1/tree/master/oscillator Project on Github] |
Latest revision as of 04:52, 7 July 2022
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
By now, timer, dma and the dac is fully configured and will run off of the buffer defined. Unfortunately, the buffer is filled with zeros, so we need to fill some data into it.
The Interrupt handler will be called twice during the run of the full buffer. The full buffer got 96 elements, so the interrupt will be generated after 48 values have been transferred to the DAC, and then again after the full 96.
__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); }
After the full transfer we can update the second half of the buffer and after the halfway point we can update the first.
The actual calculation becomes quite simple. Rather than calculating based on frequency the frequency is turned into an angular speed (in angular change per sample), so we simply add this value to the total value at each sample. We also make sure the angle never get above 2 * PI (full circle) by subtracting 2PI when it does get too large.
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)); } }
More Videos
Dual oscillator slightly out of sync (250 Hz and 249.9 Hz):
X-Y plot: