STM32实战六 PWM加移相正交

这一章编写PWM程序,使用TIM3以两个通道,完全映射到PC6和PC7,除普通PWM输出外,增加移相正交PWM功能,为后面的编码器计数模式提供信号源。

PWM.h

#ifndef __PWM__
#define __PWM__

extern "C" {				// 兼容C,按C语言编译,Keil5中的包含文件已经加入了C++兼容,不用再加这一段
#pragma diag_remark 368		//消除 warning:  #368-D: class "<unnamed>" defines no constructor to initialize the following:

#include "stm32f10x.h"

#pragma diag_default 368	// 恢复368号警告
}

#include "Timer.h"
#include "IO.h"

class PWM : public IO, public Timer
{
// Construction
public:
	PWM();

// Properties
public:

protected:
	
private:

// Methods
public:
	void setData(u16 ch, u16 nData);
	void orthogonal( u16 nArr, u16 nPrescaler );	// 生成正交波型
	void orthogonal2( u16 nArr, u16 nPrescaler );	// 生成反向正交波型
		
// Overwrite
public:
};

#endif

PWM.cpp 

/**
  ******************************************************************************
  * @file  PWM.cpp
  * @author  Mr. Hu
  * @version  V1.0.0 STM32F103VET6
  * @date  05/21/2019
  * @brief PWM
  * @IO
  *		TIM3	定时器3
  *		PC6		PWM1
  *		PC7		PWM2
  *		PC8		PWM预留
  *		PC9		PWM预留
  ******************************************************************************
  * @remarks
  * 1KHz PWM 刻度1000
  *	只用TIM3通道1和2,对应PC6和PC7
  *
  *	参考资料:
  *	https://www.cnblogs.com/zhoubatuo/p/6135103.html 有端口分配图
  *	https://blog.csdn.net/qq_36554582/article/details/81239628 完整代码
  *	https://blog.csdn.net/zqingyaa/article/details/86255208 分频计算方法
  */ 

/* Includes ------------------------------------------------------------------*/
extern "C" {	// 兼容C,按C语言编译,Keil5中的包含文件已经加入了C++兼容,不用再加这一段
#pragma diag_remark 368			//消除 warning:  #368-D: class "<unnamed>" defines no constructor to initialize the following:

#include "stm32f10x_tim.h"

#pragma diag_default 368	// 恢复368号警告
}

#include "PWM.h"

/**
  * @date	05/21/2019
  * @brief  PWM,脉宽调制
: *		IO(GPIOC, GPIO_Pin_6 | GPIO_Pin_7, GPIO_Mode_AF_PP, 1)
  *		初始化PC6、PC7,GPIO_Mode_AF_PP复用输出
  *		最后一个参数1表示初始化为高电位,但实际是低电位
  *		默认产生同相PWM,频率1k,占空比调节范围0-999,用setData设置
  *		调用成员函数orthogonal时,两个输出端PC6、PC7产生正交PWM波形,占空比为50%,
  *		主要用于模拟编码输出信号。
  *		orthogonal2产生反向的正交波形,用于编码器递减计数。
  * @param	None
  * @retval None
  */
PWM::PWM()
: IO(GPIOC, GPIO_Pin_6 | GPIO_Pin_7, GPIO_Mode_AF_PP, 1)	// GPIOx, nPin, GPIO_Mode_AF_PP 上拉, 2 输入时无效
, Timer(TIM3)	// 用TIM3
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);// 使能复用输出,不映射端口时可以不用这一句
    GPIO_PinRemapConfig(GPIO_FullRemap_TIM3, ENABLE);	// 把TIM3重映射到PC6-9,只用PC6-7。如果不映射,不要这一句

	// 同相PWM,两个通道同相
	TIM_TimeBaseStructure.TIM_Period = 1000-1;			// 自动重装载寄存器的值 // https://blog.csdn.net/zqingyaa/article/details/86255208 分频计算方法
	TIM_TimeBaseStructure.TIM_Prescaler = 72-1; 		// TIMX预分频的值,主频72M,刻度100,分频720,得到1kHz的PWM
	TIM_TimeBaseInit(m_pTIMx, &TIM_TimeBaseStructure);	// 初始化
	
	// 设置CH1的PWM模式,输出GPC6
	TIM_OCInitTypeDef  TIM_OCInitStructure;							// 定义结构体
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;				// 选择定时器模式,TIM脉冲宽度调制模式2,两种方式是反相
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;	// 比较输出使能
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;		// 输出比较极性低
	TIM_OC1Init(m_pTIMx, &TIM_OCInitStructure);						// 根据结构体信息进行初始化
	TIM_OC1PreloadConfig(m_pTIMx, TIM_OCPreload_Enable);  			// 使能定时器在CCR1上的预装载值

	// 设置CH2的PWM模式,输出GPC7
	TIM_OC2Init(m_pTIMx, &TIM_OCInitStructure);				// 根据结构体信息进行初始化
	TIM_OC2PreloadConfig(m_pTIMx, TIM_OCPreload_Enable);  	// 使能定时器在CCR2上的预装载值
}

/**
  * @date	05/21/2019
  * @brief  两个输出端正交输出
  *		此函数执行后,生成占空比为50%的正交方波,不用再执行setData
  * @param	nArr自动重装载寄存器的值
  * @param	nPrescaler,预分频数
  * @retval None
  */
void PWM::orthogonal( u16 nArr, u16 nPrescaler )
{
	// 移相PWM,两个通道正交,用于编码器模式测试。 http://www.eeworld.com.cn/mcu/2018/ic-news070140135.html
	// 不用在主循环中加设置	pwm.setData(0, 3); pwm.setData(1, 1);
	// 最大PWM频率 36/2 = 18M,18M时编码器计数器不工作。
	
	// 同相PWM,两个通道同相
	TIM_TimeBaseStructure.TIM_Period = nArr;			// 自动重装载寄存器的值 // https://blog.csdn.net/zqingyaa/article/details/86255208 分频计算方法
	TIM_TimeBaseStructure.TIM_Prescaler = nPrescaler;	// TIMX预分频的值,主频72M,刻度100,分频720,得到1kHz的PWM
	TIM_TimeBaseInit(m_pTIMx, &TIM_TimeBaseStructure);	// 初始化
	
	// 设置CH1的PWM模式,输出GPC6
	TIM_OCInitTypeDef  TIM_OCInitStructure;							// 定义结构体
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Toggle;				// 选择定时器模式,TIM脉冲宽度调制模式2,两种方式是反相
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;	// 比较输出使能
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;		// 输出比较极性低
	TIM_OCInitStructure.TIM_Pulse = 0;   							// 第一个通道不移相
	TIM_OC1Init(m_pTIMx, &TIM_OCInitStructure);						// 根据结构体信息进行初始化
	TIM_OC1PreloadConfig(m_pTIMx, TIM_OCPreload_Enable);  			// 使能定时器在CCR1上的预装载值

	// 设置CH2的PWM模式,输出GPC7
	TIM_OCInitStructure.TIM_Pulse = (nArr + 1) / 2;   		// 第二个通道移相,TIM_Period减到1时翻转,原值的一半,正好正交
	TIM_OC2Init(m_pTIMx, &TIM_OCInitStructure);				// 根据结构体信息进行初始化
	TIM_OC2PreloadConfig(m_pTIMx, TIM_OCPreload_Enable);  	// 使能定时器在CCR2上的预装载值
}

/**
  * @date	05/21/2019
  * @brief  两个输出端正交输出,反向,用于编码器递减计数
  *		此函数执行后,生成占空比为50%的正交方波,不用再执行setData
  * @param	nArr自动重装载寄存器的值
  * @param	nPrescaler,预分频数
  * @retval None
  */
void PWM::orthogonal2( u16 nArr, u16 nPrescaler )
{
	// 移相PWM,两个通道正交,用于编码器模式测试。 http://www.eeworld.com.cn/mcu/2018/ic-news070140135.html
	// 不用在主循环中加设置	pwm.setData(0, 3); pwm.setData(1, 1);
	// 最大PWM频率 36/2 = 18M,18M时编码器计数器不工作。
	
	// 同相PWM,两个通道同相
	TIM_TimeBaseStructure.TIM_Period = nArr;			// 自动重装载寄存器的值 // https://blog.csdn.net/zqingyaa/article/details/86255208 分频计算方法
	TIM_TimeBaseStructure.TIM_Prescaler = nPrescaler;	// TIMX预分频的值,主频72M,刻度100,分频720,得到1kHz的PWM
	TIM_TimeBaseInit(m_pTIMx, &TIM_TimeBaseStructure);	// 初始化
	
	// 设置CH1的PWM模式,输出GPC6
	TIM_OCInitTypeDef  TIM_OCInitStructure;							// 定义结构体
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Toggle;				// 选择定时器模式,TIM脉冲宽度调制模式2,两种方式是反相
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;	// 比较输出使能
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;		// 输出比较极性低
	TIM_OCInitStructure.TIM_Pulse = (nArr + 1) / 2;					// 第一个通道移相,TIM_Period减到1时翻转,原值的一半,正好正交
	TIM_OC1Init(m_pTIMx, &TIM_OCInitStructure);						// 根据结构体信息进行初始化
	TIM_OC1PreloadConfig(m_pTIMx, TIM_OCPreload_Enable);  			// 使能定时器在CCR1上的预装载值

	// 设置CH2的PWM模式,输出GPC7
	TIM_OCInitStructure.TIM_Pulse = 0;   					//第二个通道不移相
	TIM_OC2Init(m_pTIMx, &TIM_OCInitStructure);				// 根据结构体信息进行初始化
	TIM_OC2PreloadConfig(m_pTIMx, TIM_OCPreload_Enable);  	// 使能定时器在CCR2上的预装载值
}

/**
  * @date	05/21/2019
  * @brief  设置占空比,正交方式时不用这个设置
  * @param	ch 0通道1,否则通道2
  * @param	nData 宽度0-999
  * @retval None
  */
void PWM::setData(u16 ch, u16 nData)
{
	if( ch )
		TIM_SetCompare2(m_pTIMx, nData);	// 通道2,PC7
	else
		TIM_SetCompare1(m_pTIMx, nData);	// 通道1,PC6
}

Main.h

#ifndef __PWM__
#define __PWM__

extern "C" {				// 兼容C,按C语言编译,Keil5中的包含文件已经加入了C++兼容,不用再加这一段
#pragma diag_remark 368		//消除 warning:  #368-D: class "<unnamed>" defines no constructor to initialize the following:

#include "stm32f10x.h"

#pragma diag_default 368	// 恢复368号警告
}

#include "Timer.h"
#include "IO.h"

class PWM : public IO, public Timer
{
// Construction
public:
	PWM();

// Properties
public:

protected:
	
private:

// Methods
public:
	void setData(u16 ch, u16 nData);
	void orthogonal( u16 nArr, u16 nPrescaler );	// 生成正交波型
	void orthogonal2( u16 nArr, u16 nPrescaler );	// 生成反向正交波型
		
// Overwrite
public:
};

#endif

Main.cpp

/**
  ******************************************************************************
  * @file  Main.cpp
  * @author  Mr. Hu
  * @version  V1.0.0 STM32F103VET6
  * @date  05/18/2019
  * @brief  程序入口
	*	@io
	*		TIM3	PWM
	*		TIM4	Encode
	*		TIM7	通用定时器
	*		ADC1	ADC
	*		DAC1
	*		DAC2
	*
	*		PA0-PA3 ADC
	*		PA4		DAC1输出
	*		PA5		DAC2输出
	*		PA6 	ADC预留
	*		PA7 	ADC预留
	*		PA9 	板载串口
	*		PA10 	板载串口
	*		PA13	板载JLINK占用
	*		PA14	板载JLINK占用
	*		PA15	板载JLINK占用
	*
	*		PB1		板载SW2
	*		PB3		板载JLINK占用
	*		PB4		板载JLINK占用,部分映像的通道1不能用,所以用了没有得映像
	*		PB6		编码器 A
	*		PB7		编码器 B
	*		PB8		板载CAN
	*		PB9		板载CAN
	*		PB10	板载RS485
	*		PB11	板载RS485
	*		PB13	板载LED2
	*		PB14	板载LED3
	*		PB15	板载SW3
	*
	*		PC4		板载RS485
	*		PC5		板载RS485
	*		PC6		PWM1
	*		PC7		PWM2
	*		PC8		PWM预留
	*		PC9		PWM预留
	******************************************************************************
  * @remarks
  *
  */ 

extern "C" {	// 兼容C,按C语言编译,Keil5中的包含文件已经加入了C++兼容,不用再加这一段
#pragma diag_remark 368			//消除 warning:  #368-D: class "<unnamed>" defines no constructor to initialize the following:

#include "stm32f10x_tim.h"
#include "stm32f10x_dac.h"

#pragma diag_default 368	// 恢复368号警告
}

#include "stm32f10x_adc.h"
#include "IO.h"
#include "Timer.h"
#include "GeneralTimer.h"
#include "BoardLED.h"
#include "PWM.h"
#include "Main.h" 

/**
  * @date	05/18/2019
  * @brief  主入口,主循环
  *			如果不正常运行,可能是栈设置不够 startup_stm32f10x_hd.s Stack_Size EQU 0x600
  * @param	None
  * @retval None
*/
int main(void)
{
	SystemInit();	// 配置系统时钟为72M

	GeneralTimer tim(TIM2);		// 通用定时器,实际用TIM7,不占用IO,但软件仿真只有1-4,所以选2
	BoardLED boardLED( &tim );	// 板载LED
	
	// 板载按键,PB1 SW2, PB2 SW3,不同的板子不一样。
	IO key(GPIOC, GPIO_Pin_1 | GPIO_Pin_15, GPIO_Mode_IPU, 2);	// GPIOx, nPin, GPIO_Mode_IPU 上拉, 2 输入时无效
	
	// 使能按键滤波
	//tim.inb[1].level = 1;		// SW2 PB1 上拉
	tim.inb[1].enable = 1;		// SW2 PB1 使能
	//tim.inb[15].level = 1;	// SW3 PB15 上拉
	tim.inb[15].enable = 1;		// SW3 PB15
	
	u32 loopCount = 0;	// 主循环计数
	
	PWM pwm;
	//pwm.orthogonal( 2 - 1, 1000 - 1 );
	
	while(1)
	{
		tim.loop();	// 必须放在主循环的第一行,按键滤波和上下沿微分。
		
		// PWM
		pwm.setData(0, 300);	// PWM1 PC6 30%的占空比
		pwm.setData(1, 700);	// PWM2 PC7 70%的占空比
		
		// LED
		// 测试时间
		loopCount++;
		if( !tim.m_t[2] )	// 定时器2
		{
			tim.m_t[2] = 1000;		// 延时1000ms
			boardLED.m_nNum = 100 * 1000 / loopCount;	// 计算循环周期,1000*1000对应周期单位是1us,100*1000是10us,以此类推。
			if( boardLED.m_nNum > 0xf )
				boardLED.m_nNum = 0xf;		// 大于15时,显示15
			loopCount = 0;
		}
		boardLED.showNumber();	// 显示四位二进制boardLED.m_nNum,用了m_t[0]

		// 开关LED
		if( tim.inb[1].down | tim.inb[15].down ) 	// 两个板载开关的下降沿
		{
			boardLED.showLED(GPIO_Pin_14, 1);		// 点亮LED3
		}
		else if( tim.inb[1].up | tim.inb[15].up )	// 两个板载开关的上升沿
		{
			boardLED.showLED(GPIO_Pin_14, 0);		// 熄灭LED3
		}

	}
}

 以上代码再加上前几章的代码,运行结果如下

移相正交模式 对Main.cpp进行如下修改

	PWM pwm;
	pwm.orthogonal( 2 - 1, 1000 - 1 );
	
	while(1)
	{
		tim.loop();	// 必须放在主循环的第一行,按键滤波和上下沿微分。
		
		// PWM
		//pwm.setData(0, 300);	// PWM1 PC6 30%的占空比
		//pwm.setData(1, 700);	// PWM2 PC7 70%的占空比

运行波形如下:

开头有些不稳定,稳定后两个方波相差90度,移相正交输出,用于模拟编码器。

STM32实战系列源码,按键/定时器/PWM/ADC/DAC/DMA/滤波
STM32实战一 初识单片机
STM32实战二 新建工程
STM32实战三 C++ IO.cpp
STM32实战四 定时器和按键
STM32实战五 板载LED显示数据
STM32实战六 PWM加移相正交
STM32实战七 数字滤波
STM32实战八 DAC/ADC
STM32实战九 编码器
STM32开发过程的常见问题

  • 3
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值