前话:本文的所有程序与仿真,均以硬件的形式运行过,实际电机运行的效果图或者视频就没有贴出来了。硬件采用以DSPIC33FJ32MC204为核心的驱动电路。
注1:因为步进电机存在低频振荡,所以我们选择的运行速度稍微大一点(这个速度在其他永磁同步电机,无刷直流电机中也算是比较低的速度),速度选择为200RPM,程序中定时器定时结束进入中断的时间与这个速度息息相关。
注2:程序中注释部分可能有所纰漏或者不对,但是代码是正常的,因为注释是我在调试过程中随手记下来的,只能用作参考,不能完全相信。
注3:步进电机整步与半步相关原理可以看我之前出的两篇文章dspic33FJ32MC204开发板驱动步进电机原理(2)
dspic33FJ32MC204开发板驱动步进电机原理(1)
注4:关于Proteus仿真,有个很重要的点,那就是Proteus没办法切换时钟源,如果while等待切换时钟源,那么程序会卡死,因为没有办法成功切换时钟源。所以必须把切换时钟源的代码部分注释,默认配置程序中的FRC时钟源进行仿真。
程序思路:
首先当然是初始化外设(IO口,定时器,中断,PWM,时钟源)
然后整步与半步是查表运行每一步,所以我们分别定义整步与半步的数组,用来存放驱动步序。
步序可以看我之前的两篇文章,也可参阅其他资料看看整步与半步运行原理。
unsigned int FullStep[2][4] = {{0x002A,0x0025,0x0015,0x001A},{0x0002,0x0002,0x0001,0x0001}};
unsigned int HalfStep[2][8] = {{0x000A,0x002A,0x0020,0x0025,0x0005,0x0015,0x0010,0x001A},{0x0000,0x0002,0x0001}};
数组里面的数据与P1OVDCON和P2OVDCON两个改写控制寄存器还有步序有关。
采用定时器1产生中断,在定时中断程序中查表切换步序。定时器定时时间与电机运行的速度RPM有关。时间的计算也同样参考我之前的两篇文章。
状态机设置三个状态,初始状态,启动状态,停止状态,三个状态由一个按键负责,采用if-else语句切换判断。当然我们也需要加入按键消抖程序(虽然硬件电路有电容消抖,但我们还是插入按键消抖程序)。
整步程序:
#include "p33FJ32MC204.h"
#include "dsp.h"
#include <xc.h>
#include <PPS.H>
/*****************Config bit settings* ***************/
// FBS
#pragma config BWRP = WRPROTECT_OFF // Boot Segment Write Protect (Boot Segment may be written)
#pragma config BSS = NO_FLASH // Boot Segment Program Flash Code Protection (No Boot program Flash segment)
// FGS
#pragma config GWRP = OFF // General Code Segment Write Protect (User program m++emory is not write-protected)
#pragma config GSS = OFF // General Segment Code Protection (User program memory is not code-protected)
// FOSCSEL
#pragma config FNOSC = FRC //改了 初始振荡源选择FRC Oscillator Mode (Internal Fast RC (FRC) with divide by N)
#pragma config IESO = ON // 使用内部FRC振荡源启动器件,然后自动切换为就绪的用户选择的振荡器源 Internal External Switch Over Mode (Start-up device with FRC, then automatically switch to user-selected oscillator source when ready)
// FOSC
#pragma config POSCMD = XT //改了 Primary Oscillator Source (Primary Oscillator Disabled)
#pragma config OSCIOFNC = OFF // OSC2 Pin Function (OSC2 pin has clock out function)
#pragma config IOL1WAY = OFF // 改了 外设引脚选择配置位 允许多次配置 Peripheral Pin Select Configuration (Allow Only One Re-configuration)
#pragma config FCKSM = CSECME // 改了 必须要使得时钟使能 Clock Switching and Monitor (Both Clock Switching and Fail-Safe Clock Monitor are disabled)
// FWDT
#pragma config FWDTEN = OFF // 改了 把看门狗关闭了 Watchdog Timer Enable (Watchdog timer always enabled)
// FPOR
#pragma config FPWRT = PWR128 // POR Timer Value (128ms)
#pragma config ALTI2C = OFF // Alternate I2C pins (I2C mapped to SDA1/SCL1 pins)
#pragma config LPOL = ON // Motor Control PWM Low Side Polarity bit (PWM module low side output pins have active-high output polarity)
#pragma config HPOL = ON // Motor Control PWM High Side Polarity bit (PWM module high side output pins have active-high output polarity)
#pragma config PWMPIN = ON // Motor Control PWM Module Pin Mode bit (PWM module pins controlled by PORT register at device Reset)
// FICD
#pragma config ICS = PGD3 // Comm Channel Select (Communicate on PGC1/EMUC1 and PGD1/EMUD1)
#pragma config JTAGEN = OFF // JTAG Port Enable (JTAG is Disabled)
void Clock_InitSet()
{
//Perform a clock switch to 40MIPS (80Mhz) 外部晶振电路图上是7.3728MHZ(FIN)但是实际板子上是8MHZ
PLLFBD = 38; //M=PLLFBD+2 = 40
CLKDIVbits.PLLPOST = 0; //N2=2(PLLPOST+1) = 2
CLKDIVbits.PLLPRE = 0; //N1=PLLPRE+2 = 2
//unlock OSCCON register
__builtin_write_OSCCONH(0x03);//切换到带PLL的主振荡器模式 经过测试 应该切换成功了
__builtin_write_OSCCONL(0x01);//时钟切换被使能,外设引脚未锁定,允许写入外设引脚选择寄存器,PLL处于失锁状态,禁止辅助振荡器,请求切换新的振荡器
//wait for clock to stabilize
//while(OSCCONbits.COSC != 0b011);
//wait for PLL to lock
//while(OSCCONbits.LOCK != 1) {};
//clock switch finished
}
void PWM_InitSet()
{
//PWM配置
//PWM工作模式
P1TCON = 0x0000;//PWM时基在占空比双重更新的递增递减运行模式下工作,PWM时基在空闲模式下运行,预分频比1:1,后分频比1:1
P2TCON = 0x0000;
P1TPER = 1999;//计数周期 PWM输出10KHZ 如果没有注释计数周期这两行,测得第一个LED周期为6ms,对应200rpm。注释后周期不为6ms,有时在5.6ms,有时在6.5ms,导致电机振荡和噪音很大。
P2TPER = 1999;
//MCPWM模块占空比初始化
P1DC1 = 2000;
P1DC2 = 2000;
P1DC3 = 2000;
P2DC1 = 2000;
//MCPWM模块输出模式选择
PWM1CON1 = 0x0077;//互补输出模式,引脚用于PWM输出
PWM1CON2 = 0x0002;//特殊事件触发信号后分频比1:1,更新有效占空比寄存器与PWM时基同步,通过PXOVDCON寄存器进行的输出改写与PWM时基同步 这里的输出改写比较重要
PWM2CON1 = 0x0011;//互补输出模式
PWM2CON2 = 0x0002;//特殊事件触发信号后分频比1:1,更新有效占空比寄存器与PWM时基同步,通过PXOVDCON寄存器进行的输出改写与PWM时基同步
//死区插入(仅限互补PWM输出模式)
P1DTCON1bits.DTAPS = 2;// Clck DeadTime = 4 Tcy
P1DTCON1bits.DTA = 5;//Dead time = 5*4*25ns = 500ns
P2DTCON1bits.DTAPS = 2;
P2DTCON1bits.DTA = 5;//设定A死区值
P1DTCON2 = 0;//死区都由死区单元A提供
P2DTCON2 = 0;
//改写PWM1,PWM2初始化引脚为无效状态
P2OVDCON = 0;
P1OVDCON = 0;
//PWM1中断
IFS3bits.PWM1IF = 0;
IEC3bits.PWM1IE = 0;
IPC14bits.PWM1IP = 2;
//PWM2中断
IFS4bits.PWM2IF = 0;
IEC4bits.PWM2IE = 0;
IPC18bits.PWM2IP = 2;
//失能PWM1,PWM2
P1TCONbits.PTEN = 0;
P2TCONbits.PTEN = 0;
}
void timer1_InitSet()
{
//Timer 1 中断设置
// SRbits.IPL=0b010;
// CORCONbits.IPL3=0;
// INTCON1bits.NSTDIS = 0;//0允许中断嵌套,1禁止中断嵌套 默认是允许的
// INTCON2bits.ALTIVT = 0;
IFS0bits.T1IF = 0; // Clear Timer1 Interrupt Flag
IEC0bits.T1IE = 1; // Enable Timer1 interrupt
IPC0bits.T1IP = 3; // Set Timer 1 Interrupt Priority Level
//TMR1设置,延时2.5ms
TMR1 = 0;// Resetting TIMER
T1CONbits.TGATE = 0; //disable gated timer mode
T1CON = 0x0000; // reset timer configuration
PR1 = 7499; //load the period value FRC的值5786 PLL_XT的值62499
T1CONbits.TCKPS = 1; // 1 预分频比为1:8
//return 0;
}
//函数声明段
void StateMachine();
//变量定义段
//120rpm 整步 step1-step4 每步计算得到Timer1中断触发时间为2.5ms
//查表P1OVDCON,P2OVDCON
unsigned int FullStep[2][4] = {{0x002A,0x0025,0x0015,0x001A},{0x0002,0x0002,0x0001,0x0001}};
int i=0;
int n;
int stepcount = 0;
int state;
int statecopy;
int buttonCounter;
int BUTTON_FILTER = 20;
int buttonflag = 0;
//宏定义段
#define BUTTON_PIN PORTBbits.RB7 //按键引脚BTN
//State machine defines
#define STATE_OFF 0
#define STATE_RUN 1
#define STATE_INIT 2
int main(void)
{
//TRISCbits.TRISC3 = 0;
//TRISCbits.TRISC9 = 0;
//程序初始化
Clock_InitSet();
PWM_InitSet();
timer1_InitSet();
//设置状态机状态
state = STATE_INIT;
while(1)//原地踏步
{
//read buttonCounter state and debounce
if((BUTTON_PIN == 0) && (buttonCounter <= BUTTON_FILTER))
{
//一直做消除抖动计数,确保按键按下
buttonCounter ++;
}
if(buttonflag == 1)
{
state = STATE_RUN;
}
else
{
state = STATE_OFF;
}
//call state machine
StateMachine();
}
}
void StateMachine(void)
{
//状态处于启动
if(state == STATE_RUN)
{
if( buttonCounter > BUTTON_FILTER) //read buttonCounter state
{
if(BUTTON_PIN == 1)
{
buttonCounter = 0;
buttonflag=!buttonflag;
//使能PWM1,PWM2
P1TCONbits.PTEN = 1;
P2TCONbits.PTEN = 1;
T1CONbits.TON = 1; // Enable Timer1 定时器开启了就一直在工作,除了休眠和空闲模式
}
}
}
else
//状态处于停止
if (state==STATE_OFF)
{
if( buttonCounter > BUTTON_FILTER)
{
if(BUTTON_PIN == 1)
{
buttonCounter = 0;
buttonflag=!buttonflag;
//失能PWM
P1TCONbits.PTEN = 0;
P2TCONbits.PTEN = 0;
P2OVDCON = 0;
P1OVDCON = 0;
T1CONbits.TON = 0; // Enable Timer1 定时器开启了就一直在工作,除了休眠和空闲模式
}
}
}
//end of the OFF state
else
//状态处于初始化
if (state==STATE_INIT) //init state
{
statecopy = state; //sync variables
}
//end of the INIT state
}
void __attribute__((__interrupt__,no_auto_psv)) _T1Interrupt(void)
{
if(stepcount < 4)
{
switch(stepcount)
{
case 0: P1OVDCON = FullStep[0][0];P2OVDCON = FullStep[1][0];break;//A+,B+
case 1: P1OVDCON = FullStep[0][1];P2OVDCON = FullStep[1][1];break;//B+,A-
case 2: P1OVDCON = FullStep[0][2];P2OVDCON = FullStep[1][2];break;//A-,B-
case 3: P1OVDCON = FullStep[0][3];P2OVDCON = FullStep[1][3];break;//B-,A+
}
stepcount++;
}
else
{
stepcount = 0;
P1OVDCON = FullStep[0][0];P2OVDCON = FullStep[1][0];
stepcount++;
}
IFS0bits.T1IF = 0;
}
/* PWM1 Interrupt */
void __attribute__((__interrupt__,auto_psv)) _MPWM1Interrupt(void)
{
IFS3bits.PWM1IF = 0;/* Timer 1 Interrupt */ //下面这段程序运行了
}
/* PWM2 Interrupt */
void __attribute__((__interrupt__,auto_psv)) _MPWM2Interrupt(void)
{
IFS4bits.PWM2IF = 0;
}
半步程序:
#include "p33FJ32MC204.h"
#include "dsp.h"
#include <xc.h>
#include <PPS.H>
/*****************Config bit settings* ***************/
// FBS
#pragma config BWRP = WRPROTECT_OFF // Boot Segment Write Protect (Boot Segment may be written)
#pragma config BSS = NO_FLASH // Boot Segment Program Flash Code Protection (No Boot program Flash segment)
// FGS
#pragma config GWRP = OFF // General Code Segment Write Protect (User program m++emory is not write-protected)
#pragma config GSS = OFF // General Segment Code Protection (User program memory is not code-protected)
// FOSCSEL
#pragma config FNOSC = FRC //改了 初始振荡源选择FRC Oscillator Mode (Internal Fast RC (FRC) with divide by N)
#pragma config IESO = ON // 使用内部FRC振荡源启动器件,然后自动切换为就绪的用户选择的振荡器源 Internal External Switch Over Mode (Start-up device with FRC, then automatically switch to user-selected oscillator source when ready)
// FOSC
#pragma config POSCMD = XT //改了 Primary Oscillator Source (Primary Oscillator Disabled)
#pragma config OSCIOFNC = OFF // OSC2 Pin Function (OSC2 pin has clock out function)
#pragma config IOL1WAY = OFF // 改了 外设引脚选择配置位 允许多次配置 Peripheral Pin Select Configuration (Allow Only One Re-configuration)
#pragma config FCKSM = CSECME // 改了 必须要使得时钟使能 Clock Switching and Monitor (Both Clock Switching and Fail-Safe Clock Monitor are disabled)
// FWDT
#pragma config FWDTEN = OFF // 改了 把看门狗关闭了 Watchdog Timer Enable (Watchdog timer always enabled)
// FPOR
#pragma config FPWRT = PWR128 // POR Timer Value (128ms)
#pragma config ALTI2C = OFF // Alternate I2C pins (I2C mapped to SDA1/SCL1 pins)
#pragma config LPOL = ON // Motor Control PWM Low Side Polarity bit (PWM module low side output pins have active-high output polarity)
#pragma config HPOL = ON // Motor Control PWM High Side Polarity bit (PWM module high side output pins have active-high output polarity)
#pragma config PWMPIN = ON // Motor Control PWM Module Pin Mode bit (PWM module pins controlled by PORT register at device Reset)
// FICD
#pragma config ICS = PGD3 // Comm Channel Select (Communicate on PGC1/EMUC1 and PGD1/EMUD1)
#pragma config JTAGEN = OFF // JTAG Port Enable (JTAG is Disabled)
void Clock_InitSet()
{
//Perform a clock switch to 40MIPS (80Mhz) 外部晶振电路图上是7.3728MHZ(FIN)但是实际板子上是8MHZ
PLLFBD = 38; //M=PLLFBD+2 = 40
CLKDIVbits.PLLPOST = 0; //N2=2(PLLPOST+1) = 2
CLKDIVbits.PLLPRE = 0; //N1=PLLPRE+2 = 2
//unlock OSCCON register
__builtin_write_OSCCONH(0x03);//切换到带PLL的主振荡器模式 经过测试 应该切换成功了
__builtin_write_OSCCONL(0x01);//时钟切换被使能,外设引脚未锁定,允许写入外设引脚选择寄存器,PLL处于失锁状态,禁止辅助振荡器,请求切换新的振荡器
//wait for clock to stabilize
//while(OSCCONbits.COSC != 0b011);
//wait for PLL to lock
//while(OSCCONbits.LOCK != 1) {};
//clock switch finished
}
void PWM_InitSet()
{
//PWM配置
//PWM工作模式
P1TCON = 0x0000;//PWM时基在占空比双重更新的递增递减运行模式下工作,PWM时基在空闲模式下运行,预分频比1:1,后分频比1:1
P2TCON = 0x0000;
P1TPER = 1999;//计数周期 PWM输出10KHZ 如果没有注释计数周期这两行,测得第一个LED周期为6ms,对应200rpm。注释后周期不为6ms,有时在5.6ms,有时在6.5ms,导致电机振荡和噪音很大。
P2TPER = 1999;
//MCPWM模块占空比初始化
P1DC1 = 2000;
P1DC2 = 2000;
P1DC3 = 2000;
P2DC1 = 2000;
//MCPWM模块输出模式选择
PWM1CON1 = 0x0077;//互补输出模式,引脚用于PWM输出
PWM1CON2 = 0x0002;//特殊事件触发信号后分频比1:1,更新有效占空比寄存器与PWM时基同步,通过PXOVDCON寄存器进行的输出改写与PWM时基同步 这里的输出改写比较重要
PWM2CON1 = 0x0011;//互补输出模式
PWM2CON2 = 0x0002;//特殊事件触发信号后分频比1:1,更新有效占空比寄存器与PWM时基同步,通过PXOVDCON寄存器进行的输出改写与PWM时基同步
//死区插入(仅限互补PWM输出模式)
P1DTCON1bits.DTAPS = 2;// Clck DeadTime = 4 Tcy
P1DTCON1bits.DTA = 5;//Dead time = 5*4*25ns = 500ns
P2DTCON1bits.DTAPS = 2;
P2DTCON1bits.DTA = 5;//设定A死区值
P1DTCON2 = 0;//死区都由死区单元A提供
P2DTCON2 = 0;
//改写PWM1,PWM2初始化引脚为无效状态
P2OVDCON = 0;
P1OVDCON = 0;
//PWM1中断
IFS3bits.PWM1IF = 0;
IEC3bits.PWM1IE = 0;
IPC14bits.PWM1IP = 2;
//PWM2中断
IFS4bits.PWM2IF = 0;
IEC4bits.PWM2IE = 0;
IPC18bits.PWM2IP = 2;
//失能PWM1,PWM2
P1TCONbits.PTEN = 0;
P2TCONbits.PTEN = 0;
}
void timer1_InitSet()
{
//Timer 1 中断设置
// SRbits.IPL=0b010;
// CORCONbits.IPL3=0;
// INTCON1bits.NSTDIS = 0;//0允许中断嵌套,1禁止中断嵌套 默认是允许的
// INTCON2bits.ALTIVT = 0;
IFS0bits.T1IF = 0; // Clear Timer1 Interrupt Flag
IEC0bits.T1IE = 1; // Enable Timer1 interrupt
IPC0bits.T1IP = 3; // Set Timer 1 Interrupt Priority Level
//TMR1设置,延时2.5ms
TMR1 = 0;// Resetting TIMER
T1CONbits.TGATE = 0; //disable gated timer mode
T1CON = 0x0000; // reset timer configuration
PR1 = 3499; //半步运行200rpm是3499
T1CONbits.TCKPS = 1; // 1 预分频比为1:8
//return 0;
}
//函数声明段
void StateMachine();
//变量定义段
//120rpm 整步 step1-step4 每步计算得到Timer1中断触发时间为2.5ms
//查表P1OVDCON,P2OVDCON
unsigned int HalfStep[2][8] = {{0x000A,0x002A,0x0020,0x0025,0x0005,0x0015,0x0010,0x001A},{0x0000,0x0002,0x0001}};
int i=0;
int n;
int stepcount = 0;
int state;
int statecopy;
int buttonCounter;
int BUTTON_FILTER = 20;
int buttonflag = 0;
//宏定义段
#define BUTTON_PIN PORTBbits.RB7 //按键引脚BTN
//State machine defines
#define STATE_OFF 0
#define STATE_RUN 1
#define STATE_INIT 2
int main(void)
{
//TRISCbits.TRISC3 = 0;
//TRISCbits.TRISC9 = 0;
//程序初始化
Clock_InitSet();
PWM_InitSet();
timer1_InitSet();
//设置状态机状态
state = STATE_INIT;
while(1)//原地踏步
{
//read buttonCounter state and debounce
if((BUTTON_PIN == 0) && (buttonCounter <= BUTTON_FILTER))
{
//一直做消除抖动计数,确保按键按下
buttonCounter ++;
}
if(buttonflag == 1)
{
state = STATE_RUN;
}
else
{
state = STATE_OFF;
}
//call state machine
StateMachine();
}
}
void StateMachine(void)
{
//状态处于启动
if(state == STATE_RUN)
{
if( buttonCounter > BUTTON_FILTER) //read buttonCounter state
{
if(BUTTON_PIN == 1)
{
buttonCounter = 0;
buttonflag=!buttonflag;
//使能PWM1,PWM2
P1TCONbits.PTEN = 1;
P2TCONbits.PTEN = 1;
T1CONbits.TON = 1; // Enable Timer1 定时器开启了就一直在工作,除了休眠和空闲模式
}
}
}
else
//状态处于停止
if (state==STATE_OFF)
{
if( buttonCounter > BUTTON_FILTER)
{
if(BUTTON_PIN == 1)
{
buttonCounter = 0;
buttonflag=!buttonflag;
//失能PWM
P1TCONbits.PTEN = 0;
P2TCONbits.PTEN = 0;
P2OVDCON = 0;
P1OVDCON = 0;
T1CONbits.TON = 0; // Enable Timer1 定时器开启了就一直在工作,除了休眠和空闲模式
}
}
}
//end of the OFF state
else
//状态处于初始化
if (state==STATE_INIT) //init state
{
statecopy = state; //sync variables
}
//end of the INIT state
}
void __attribute__((__interrupt__,no_auto_psv)) _T1Interrupt(void)
{
if(stepcount < 8)
{
switch(stepcount)
{
case 0: P1OVDCON = HalfStep[0][0];P2OVDCON = HalfStep[1][0];break;//A+
case 1: P1OVDCON = HalfStep[0][1];P2OVDCON = HalfStep[1][1];break;//A+,B+
case 2: P1OVDCON = HalfStep[0][2];P2OVDCON = HalfStep[1][1];break;//B+
case 3: P1OVDCON = HalfStep[0][3];P2OVDCON = HalfStep[1][1];break;//B+,A-
case 4: P1OVDCON = HalfStep[0][4];P2OVDCON = HalfStep[1][0];break;//A-
case 5: P1OVDCON = HalfStep[0][5];P2OVDCON = HalfStep[1][2];break;//A-,B-
case 6: P1OVDCON = HalfStep[0][6];P2OVDCON = HalfStep[1][2];break;//B-
case 7: P1OVDCON = HalfStep[0][7];P2OVDCON = HalfStep[1][2];break;//B-,A+
}
stepcount++;
}
else
{
stepcount = 0;
P1OVDCON = HalfStep[0][0];P2OVDCON = HalfStep[1][0];//A+
stepcount++;
}
IFS0bits.T1IF = 0;
}
/* PWM1 Interrupt */
void __attribute__((__interrupt__,auto_psv)) _MPWM1Interrupt(void)
{
IFS3bits.PWM1IF = 0;/* Timer 1 Interrupt */ //下面这段程序运行了
}
/* PWM2 Interrupt */
void __attribute__((__interrupt__,auto_psv)) _MPWM2Interrupt(void)
{
IFS4bits.PWM2IF = 0;
}
Proteus仿真原理图:
仿真结果:可以看到电机在以整步或者半步运行