这一章编写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开发过程的常见问题