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開發過程的常見問題

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章