這一章編寫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開發過程的常見問題