前言
本文是基于嵌入式开发板CT117E,stm32f103RBT6。"空手套白狼"就是直接利用官方给的库(v3.5),进行拷贝修改形成可以正常运行的代码。
一、芯片手册
二、两路不同频率和不同占空比的PWM的产生原理
-
为了输出两路不同频率,不同占空比的pwm波,可以采用的输出比较模式,采用其进去中断函数会自动反转输出极性相反的电平,我们在中断里面设置比较的计数值,达到我们需要的波形,这种属于手动人工输出pwm波。
-
由于蓝桥杯的时候可能用到usart2,与定时器2的引脚冲突了,所以pwm的输出选择定时器3,PB6,PB7,通道1和通道2;
-
实现的思路:这个编程的思路需要对CCR1,CCR2,CNT,这三个寄存器的功能有足够的了解,CCR寄存器会不断地与CNT寄存器里的值做比较,两者相等的话,就产生中断,第一次产生中断的极性是在初始化配置的时候配置的,程序一开始就先让CCR和CNT都为0,一使能定时器中断就发生中断,在中断中判断是哪个通道出现中断,捕获当前CCR寄存器对的值,然后再用一个标志位判断目前是高电平还是低电平,然后再重新设置当前的CCR的值,达到我们所要求的频率和占空比。
三、定时器3初始化
- 输出pwm波需要输出引脚,需要中断,需要自身的基础定时器结构体,也需要TIM_OCInitTypeDef结构体实现比较模式,所以需要初始化4个结构体,这些都可以复制固件库里面的做稍微的修改就可以了
- 可以打开OCToggle这个文件夹里面的main.c,复制里面的162行的gpio结构体初始化,改下端口和端口号,194行的中断控制器结构体初始化。
- 接下来就是基础定时器的结构体配置,这里直接复制89行的,再把时钟预分频系数改为71,结果时钟频率为1M,也就是每一个计数1us,再复制97-101行的定时器TIM_OCInitTypeDef结构体的初始化实现输出比较模式,注意这只是通道1的初始化TIM_OC1Init,我们还需要通道二的初始化TIM_OC2Init,主要修改一下TIM_Pulse的值,这个值其实在这里没有意义,这个的值就是ccr的值,完成上面这些结构体的配置后,就要使能各个时钟,最后使能定时器3,开启定时器3的通道1和通道2的中断。
STM32固件库代码V3.5版\STM32F10x_StdPeriph_Lib_V3.5.0\Project\STM32F10x_StdPeriph_Examples\TIM\OCToggle\main.c 和 stm32f10x_it.c
u16 CH1_VAL,CH2_VAL,CH1_DUTY,CH2_DUTY;
u8 ch1_flag=0;
u8 ch2_flag=0;
void tim3_pwm_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7 ;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
TIM_TimeBaseStructure.TIM_Period = 65535;
TIM_TimeBaseStructure.TIM_Prescaler = 71;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
/* Output Compare Toggle Mode configuration: Channel1 */
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Toggle;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 65534;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;
TIM_OC1Init(TIM3, &TIM_OCInitStructure);
TIM_OC2Init(TIM3, &TIM_OCInitStructure);
TIM_Cmd(TIM3, ENABLE);
/* TIM IT enable */
TIM_ITConfig(TIM3, TIM_IT_CC1 | TIM_IT_CC2 , ENABLE);
}
void set_pwm(u16 ch1_val,u16 ch1_duty,u16 ch2_val,u16 ch2_duty)
{
CH1_VAL=1000000/ch1_val;
CH2_VAL=1000000/ch2_val; //传进来的频率,转换成定时器的计数值
CH1_DUTY=CH1_VAL*ch1_duty/100; //高电平的计时值转换
CH2_DUTY=CH2_VAL*ch2_duty/100;
//让CCR和CNT都为0
TIM_SetCounter(TIM3,0);
TIM_SetCompare1(TIM3,0);
TIM_SetCompare2(TIM3,0);
}
四、定时器3中断产生pwm
- 中断函数的编写:直接复制stm32f10x_it.c里面的150行,的通道1和2中断的内容,加上高低电平时间的判断和传入频率占空比的设置,就行了;中断函数里面的逻辑理解可以参考上面的PWM产生原理。
u8 ch2_flag=0;
u8 ch1_flag=0;
u32 capture;
void TIM3_IRQHandler(void)
{
if (TIM_GetITStatus(TIM3, TIM_IT_CC1) != RESET)
{
TIM_ClearITPendingBit(TIM3, TIM_IT_CC1 );
capture = TIM_GetCapture1(TIM3); //捕获当前的ccr寄存器的值,第一次在这里为0,后面逐渐增加
if(ch1_flag) //一开始为假,先执行else分支
{
TIM_SetCompare1(TIM3, capture + CH1_DUTY ); //设置高电平的输出时间
}
else
{
TIM_SetCompare1(TIM3, capture + CH1_VAL - CH1_DUTY ); //设置低电平的输出时间
}
ch1_flag^=1; //每次发生中断都会进行反转状态,达到高低电平间的时间切换
}
if (TIM_GetITStatus(TIM3, TIM_IT_CC2) != RESET)
{
TIM_ClearITPendingBit(TIM3, TIM_IT_CC2 ); //同上,原理一样
capture = TIM_GetCapture2(TIM3);
if(ch2_flag)
{
TIM_SetCompare2(TIM3, capture + CH2_DUTY );
}
else
{
TIM_SetCompare2(TIM3, capture + CH2_VAL - CH2_DUTY );
}
ch2_flag^=1;
}
}
五、在主函数中的应用方法
tim3_pwm_init();
set_pwm(1000,50,100,20); //产生频率为1000HZ,占空比为50%,频率为100HZ,占空比为20的两路pwm波
while(1)
{
}