这一段帮同学用AVR128A做毕设,航模遥控控制机械臂工作台,使用驱动的时候遇到了锁不住电机的问题。原来解决这个问题是两个方案:一是设置驱动的截断的电流,<多少转速是死区。第二种方法是自己用编码器写一个抱死的程序。
下面是我的程序的设计思路: 这个程序用了两个定时器:timer0和timer1。 编译环境是AVR Studio 5.0,下面是程序代码:
#include
#include
int forward = 0, reverse = 0;//存储电机正转和反转pwm占空比的变量
int timer0_count = 0;//timer0溢出事件计数器
int capt_count = 0;//输入捕捉事件计数器
void port_init(void)
{
PORTA = 0x00;
DDRA = 0x00;
PORTB = 0x00;
DDRB = 0x00;
PORTC = 0x00; //m103 output only
DDRC = 0x00;
PORTD = 0x00;
DDRD = 0xC0;
PORTE = 0x00;
DDRE = 0x00;
PORTF = 0x00;
DDRF = 0x00;
PORTG = 0x00;
DDRG = 0x00;
}
void timer0_init(void)
{
TCCR0 |= 5;//256分频,普通模式
TIMSK |= 0x01;//timer0溢出中断
TCNT0 = 0xFE;//TCNT0赋初值
}
void timer1_init(void)
{
TCCR1B = 0x00;//停止
TCCR1A = 0x00;//普通模式
TCCR1C = 0x00;
TCNT1 = 0;//计数初值
TCCR1B = 0xC4;//启动定时器,256分频,使能输入捕捉噪声抑制器,输入捕捉触发沿选择:上升沿
TIMSK = 0x24;//输入捕捉中断使能,T/C1溢出中断使能
}
/************************************************************************/
/* timer0溢出中断函数,产生提供给电机的pwm */
/************************************************************************/
ISR(TIMER0_OVF_vect)//200kHz
{
TCNT0 = 0xFE;//TCNT0重新赋值
//当timer0_count等于100时,如果正转或反转的占空比不为0,则相应引脚输出高电平
if(++timer0_count >= 100)//对timer0溢出事件计数100次,相当于100分频,最后输出到电机的pwm频率是2kHz
{
timer0_count = 0;
if(forward != 0)//forward, reverse存储电机正转和反转pwm占空比的变量
{PORTD |= (1<<6);}
if(reverse != 0)
{PORTD |=(1<<7);}
}
//当timer0_count等于正转或反转的占空比时,相应引脚输出低电平,实现输出pwm信号
if(timer0_count == forward)
{PORTD &= ~(1 << 6);}
if(timer0_count == reverse)
{PORTD &= ~(1 << 7);}
}
/************************************************************************/
/* timer1输入捕捉中断函数,对编码器输出的上升沿进行计数 */
/************************************************************************/
ISR(TIMER1_CAPT_vect)
{
if(PIND & (1 << 5))//电机反转
{capt_count--;}//输入捕捉计数器减1
else //电机正转
{capt_count++;}//输入捕捉计数器加1
}
/************************************************************************/
/* timer1溢出中断函数,100Hz,用于调整电机转速和转动的方向,实现电机抱死*/
/************************************************************************/
ISR(TIMER1_OVF_vect)
{
TCNT1 = 64910; //重新给TCNT1赋值
static unsigned char motor_state = 0; //电机的状态,标志电机是正转还是反转,0:正转,1:反转
switch(motor_state)
{
case 0://电机正转时
if(capt_count > 40) //如果编码器正转计数超过40,则电机需要反转,以保持电机不动
{reverse = capt_count - 40;} //直接把编码器计数值减去40,作为反转的占空比
else if(capt_count < 0) //如果编码器计数值小于0
{motor_state = 1;} //进入状态1
else //如果编码器计数值在0~40内,为了不发生抖动,不需要反转
{reverse = 0;} //反转的占空比为0,相当于负极接地
forward = 0; //正转的占空比为0,相当于正极接地
break;
case 1:
if(capt_count < -40) //如果编码器反转计数超过40,则电机需要正转,以保持电机不动
{forward = (-capt_count) - 40;}//直接把编码器计数值减去40,作为正转的占空比
else if(capt_count > 0) //如果编码器计数值大于0
{motor_state = 0;} //返回状态0
else //如果编码器计数值在-40~0内,为了不发生抖动,不需要正转
{forward = 0;} //正转的占空比为0,相当于正极接地
reverse = 0; //反转的占空比为0 ,相当于负极接地
break;
default:
break;
}
}
void Init_Devices(void)
{
cli();//关闭全局中断
port_init();//I/O口初始化
timer1_init();//定时/计数器1初始化
timer0_init();//计时/计数器0初始化
sei();//打开全局中断
}
int main(void)
{
Init_Devices();
while(1)
{}
return 0;
}
这是比较麻烦的方法,需要用到编码器。后来仔细再读一遍AVR的说明书。发现是PWM模式的选择有问题。下面是5种模式说明
1 普通模式 WGM1=0
跟51的普通模式差不多,有TOV1溢出中断标志,发生于MAX(0xFFFF)时
1 采用内部计数时钟 用于 ICP捕捉输入场合---测量脉宽/红外解码
(捕捉输入功能可以工作在多种模式下,而不单单只是普通模式)
2 采用外部计数脉冲输入 用于 计数,测频
其他的应用,采用其他模式更为方便,不需要像51般费神
2 CTC模式 [比较匹配时清零定时器模式] WGM1=4,12
跟51的自动重载模式差不多
1 用于输出50%占空比的方波信号
2 用于产生准确的连续定时信号
WGM1=4时,最大值由OCR1A设定,TOP时产生OCF1A比较匹配中断标志
WGM1=12时,最大值由ICF1设定, TOP时产生ICF1输入捕捉中断标志
------如果TOP=MAX,TOP时也会产生TOV1溢出中断标志
注:WGM=15时,也能实现从OC1A输出方波,而且具备双缓冲功能
计算公式: fOCn="fclk"_IO/(2*N*(1+TOP))
变量N 代表预分频因子(1、8、64、256、1024),T2多了(32、128)两级。
3 快速PWM模式 WGM1=5,6,7,14,15
单斜波计数,用于输出高频率的PWM信号(比双斜波的高一倍频率)
都有TOV1溢出中断,发生于TOP时[不是MAX,跟普通模式,CTC模式不一样]
比较匹配后可以产生OCF1x比较匹配中断.
WGM1=5时, 最大值为0x00FF, 8位分辨率
WGM1=6时, 最大值为0x01FF, 9位分辨率
WGM1=7时, 最大值为0x03FF,10位分辨率
WGM1=14时,最大值由ICF1设定, TOP时产生ICF1输入捕捉中断 (单缓冲)
WGM1=15时,最大值由OCR1A设定,TOP时产生OCF1A比较匹配中断(双缓冲,但OC1A将没有PWM能力,最多只能输出方波)
改变TOP值时必须保证新的TOP值不小于所有比较寄存器的数值
注意,即使OCR1A/B设为0x0000,也会输出一个定时器时钟周期的窄脉冲,而不是一直为低电平
计算公式:fPWM=fclk_IO/(N*(1+TOP))
4 相位修正PWM模式 WGM1=1,2,3,10,11
双斜波计数,用于输出高精度的,相位准确的,对称的PWM信号
都有TOV1溢出中断,但发生在BOOTOM时
比较匹配后可以产生OCF1x比较匹配中断.
WGM1=1时, 最大值为0x00FF, 8位分辨率
WGM1=2时, 最大值为0x01FF, 9位分辨率
WGM1=3时, 最大值为0x03FF,10位分辨率
WGM1=10时,最大值由ICF1设定, TOP时产生ICF1输入捕捉中断 (单缓冲)
WGM1=11时,最大值由OCR1A设定,TOP时产生OCF1A比较匹配中断(双缓冲,但OC1A将没有PWM能力,最多只能输出方波)
改变TOP值时必须保证新的TOP值不小于所有比较寄存器的数值
可以输出0%~100%占空比的PWM信号
若要在T/C 运行时改变TOP 值,最好用相位与频率修正模式代替相位修正模式。若TOP保持不变,那么这两种工作模式实际没有区别
计算公式:fPWM=fclk_IO/(2*N*TOP)
5 相位与频率修正PWM模式 WGM1=8,9
双斜波计数,用于输出高精度的、相位与频率都准确的PWM波形
都有TOV1溢出中断,但发生在BOOTOM时
比较匹配后可以产生OCF1x比较匹配中断.
WGM1=8时,最大值由ICF1设定, TOP时产生ICF1输入捕捉中断 (单缓冲)
WGM1=9时,最大值由OCR1A设定,TOP时产生OCF1A比较匹配中断(双缓冲,但OC1A将没有PWM能力,最多只能输出方波)
相频修正修正PWM 模式与相位修正PWM 模式的主要区别在于OCR1x 寄存器的更新时间
改变TOP值时必须保证新的TOP值不小于所有比较寄存器的数值
可以输出0%~100%占空比的PWM信号
使用固定TOP 值时最好使用ICR1 寄存器定义TOP。这样OCR1A 就可以用于在OC1A输出PWM 波。
但是,如果PWM 基频不断变化(通过改变TOP值), OCR1A的双缓冲特性使其更适合于这个应用。
计算公式:fPWM=fclk_IO/(2*N*TOP)
说一下使用的体验吧。我使用的是四个冯哈伯的24V电机,与一个5相步进电机加5807np驱动器。5相步进除了供电外,如果不设置扭矩,输出脉冲检测,步距角,只往CW和CCW输入脉冲就可以实现正反转。开始的想法是PWM直接输出给CW,CCW。发现一直转,无法停止,猜测应该是有脉冲一直输出,手头没有示波器。采用的是快速PWM模式,问题是在OCRXX设置为0的时候还是有短暂的脉冲输出。换成相位修正模式解决问题。相位修正可以直接输出0和5V。实际上,后来实验,还没有直接IO取反顺畅,跟PWM的分频有关系,频率不够快。
后面使用到了舵机,因为2个定时器用来采集,两个用来输出PWM。没有用来定时的资源了。就打算偷懒直接输PWM。使用快速或者相位修正的10位模式可以做到使其转动,但是因为周期不是20ms,对应的时间设置和数字对不上,控制也不准确。如果想准确定时,通过自己的分频计算,使用相频可调模式,设置
TCCR1A=0XA0;
TCCR1B=0X13;
TCR1=1152;
OCR1A=87;//1.5MS 360舵机的停止