This is the second day of my participation in Gwen Challenge

Recently, I need to use the SPWM wave output function of single chip microcomputer. I found a lot of information on the Internet, but found that they are not complete. Those with algorithms have no codes, and those with codes can not understand the algorithm. So they had to grope, now the method is arranged as follows. There is a lot of information on the web about what SPWM waves are and why they are used. Mainly about STM32F103C8T6 is how to achieve. To generate SPWM wave, the core is to adjust the duty cycle of PWM wave, in a certain period of time to make the output PWM wave area and the corresponding sinusoidal wave area equal. Duty cycle adjustment needs to be adjusted according to the corresponding point on the sine wave. So the first thing is to generate a set of sine wave data. The data code for generating sine waves is as follows:

Void get_sin_tab1(u16 point, u16 maxnum) {u16 I = 0, j = 0, k = 0; Float hd = 0.0; Float fz = 0.0; float fz = 0.0; // Peak value u16 tem = 0; j = point / 2; // No negative voltage hd = PI/j; // the radian value of each point in π/2 = maxnum /2; For (I = 0; i < point; i++ ) { fz = k * sin( hd * i ) + k; tem = ( u16 )fz ; printf("%d\r\n",tem); sinData[i] = tem; }Copy the code

Through the serial port waveform software, you can see the generated data and waveform as follows:

After the data is generated, set the timer output PWM

void TIM1_PWM_Init(u16 arr, u16 psc) { TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; TIM_OCInitTypeDef TIM_OCInitStructure; GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // Multiplexing clock gPIo_initstructure. GPIO_Pin = GPIO_Pin_8; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); TIM_TimeBaseInitStructure.TIM_Period = arr; TIM_TimeBaseInitStructure.TIM_Prescaler = psc; TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; // Only advanced timers need to be set. Other timers need not be set. 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_PWM1; TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OC1Init(TIM1, &TIM_OCInitStructure); //TIM1_OC1 TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable); TIM_Cmd(TIM1, ENABLE); TIM_CtrlPWMOutputs(TIM1, ENABLE); // Advanced timer must be turned on}Copy the code

Set timer 1 to output PWM wave, timer initialization as:

TIM1_PWM_Init(1000 - 1, 3); Timer 1 outputs 18K PWM wave. At this time, the duty cycle of the output PWM wave is fixed and cannot change with the sine law. Down using timer 2 timing interrupt, change the PWM duty cycle in the interrupt. // Tout= (arr+1)*(PSC +1)/Tclk void TIM2_Init(u16 arr, u16 psc) { TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); TIM_TimeBaseInitStructure.TIM_Period = arr; TIM_TimeBaseInitStructure.TIM_Prescaler = psc; TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; // Only advanced timers need to be set. Other timers need not be set. TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure); TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x01; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); TIM_Cmd(TIM2, ENABLE); } u8 dc_cnt = 0; Void TIM2_IRQHandler(void) {if(TIM_GetITStatus(TIM2, TIM_IT_Update)! = RESET) { TIM_SetCompare1(TIM1, sinData[dc_cnt]); dc_cnt++; if(dc_cnt >= PointMax) dc_cnt = 0; TIM_ClearITPendingBit(TIM2, TIM_IT_Update); }}Copy the code

During timer 2 interrupts, the sinusoidal data stored in the sinData array is successively used as the PWM duty cycle. Since the maximum generated sine data is 1000 and the ARR value of the output PWM is 1000, the generated sine value can be directly used as duty cycle. If the generated sine wave data is not the same as the ARR value, a conversion is required here, using a scaling factor to adjust the maximum value in the sine wave to be exactly the maximum duty cycle. In this way, when timer 1 outputs PWM ratio, the duty cycle of THE PWM is adjusted in the interrupt of timer 2, so that the output PWM wave passes through the external capacitor, which is a sine wave. Connect the 105 capacitor at pin PA8. The generated waveform is as follows:

After the generation of sine waves, the primary purpose is achieved. So how do you synchronize the generated sine wave with the external mains waveform? The external waveform can be measured by a zero-crossing detection circuit. A pulse is output at each zero crossing point. This pulse is then detected with interrupts, and each time the zero crossing occurs, the subscript of the sine wave array is set to 0 in the interrupt, so that at each zero crossing, the output sine wave also starts at 0. The external interrupt code is as follows:

void IO_Exti_Init(void) { EXTI_InitTypeDef EXTI_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource0); EXTI_InitStructure.EXTI_Line = EXTI_Line0; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStructure); NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); } extern u8 dc_cnt; Void EXTI0_IRQHandler(void) {if(EXTI_GetITStatus(EXTI_Line0)! = RESET) { dc_cnt = 0; EXTI_ClearITPendingBit(EXTI_Line0); }}Copy the code

In the external interrupt, the subscript control value dc_cnT of the sine wave array is cleared to zero, so that at each zero crossing, the output sine wave data is exactly the first one. In this way, the output SPWM wave can be synchronized with the external waveform through the interrupt.

It can be seen from the waveform that at the zero crossing position, the output sine wave also happens to be at zero. What if you want to adjust the initial position of the output sine wave data? All you need to do is set the initial position when generating the sine wave data.

void get_sin_tab1( u16 point, u16 maxnum ) { u16 i = 0, j = 0, k = 0; Float hd = 0.0; Float fz = 0.0; float fz = 0.0; // Peak value u16 tem = 0; j = point / 2; // Single chip microcomputer has no negative voltage horizontal line is half of the number of points; // the radian value of each point in π/2 = maxnum /2; For (I = 0; i < point; i++ ) { fz = k * sin( hd * i ) + k; //fz = k * sin(hd * I + PI / 2) + k; //fz = k * sin(hd * I + PI /2 + PI) + k; Tem = (u16)fz; sinData[i] = tem; }}Copy the code

If the generated data is sin(hd * I), the value is the initial phase. If you want to adjust the initial position, you simply add the offset to the value in parentheses. The default starting position is 0 degrees of the sine wave.

If you want to start at 270 degrees, add 3/2π to the parentheses.