基于开源ATmega8 无感BLDC程序移植到ATmega328PB
- 🔖基于
Atmel Studio 7.0
开发环境。 - 🥕开源原项目资源地址:
https://svn.mikrokopter.de/websvn/listing.php?repname=BL-Ctrl&path=%2F&
- 📍原理图和PCB资源 BL-Ctrl v2.0 in Eagle(MEGA168与本移植项目原理图差异较大):
https://github.com/Janesak1977/BLCTRL20_HW
- 🌿BL-Ctrl官网BL-Ctrl V3、BL-Ctrl V2.0、BL-Ctrl V1.2资料介绍:
https://wiki.mikrokopter.de/en/BL-Ctrl?action=show&redirect=en%2FBrushlessCtrl
- 📜BL-Ctrl历史版本查询:
https://wiki.mikrokopter.de/Ctrl_History
ATmega8 单片机和ATmega328PB差异不大,大部分寄存器无需修改,少数寄存器存在差异,硬件资源上,ATmega328PB资源外设比ATmega8多,价格上差不多。虽然采用的是8位单片机,与当今主流单片机,在性能上无法比拟。不影响学习研究。
- ✨同驱动类型,性价比更高的,国内开源公开资料的有:基于STC8和STC32单片机
梁工
的相关无刷电机驱动工程,可从STCAI论坛获取相关资料。原理实现方法相同,应该也算是借鉴过来的,后起之作。- 📍STC
梁工
开源的三相无刷直流电机驱动资料地址:https://www.stcaimcu.com/forum.php?mod=viewthread&tid=1822&highlight=%E6%97%A0%E5%88%B7%E7%94%B5%E6%9C%BA&page=1&extra=#pid11784
- 🌼移植后基于自制驱动板,驱动转动效果:
- 📌自制驱动板相关内容《自制无感无刷电机驱动板》
- ✒自己亲手移植一遍工程,不是简单的照搬一次代码,起码对整个无感无刷电机运转实现,有一个认识和了解,同时硬件上学习了2个单片机的资源外设,进行熟悉了一遍。
- 🌿ATmega8 原理图:
https://wiki.mikrokopter.de/en/BL-Ctrl_V1.2
- 🌿通过上面的
BL-Ctrl_V1.2
版本原理图移植到ATmega328PB,个人使用参考的源码是:V0.42:https://svn.mikrokopter.de/websvn/listing.php?repname=BL-Ctrl&path=%2Ftags%2FV0.42%2F&#a4bfcc0886576e3118d94460220fa558a
- 🌿ATmega328PB引脚连接参考上面的图纸:BL-Ctrl_V1.2版本原理图。相关1.2版本程序:
https://github.com/jankae/brushlessControl
- 🔨代码移植编译平台:
Atmel Studio 7.0
⛳移植难点和重点
✨整个工程,基本都是围绕着,电机转动时,反电动势检测过零点内容的实现。对于PWM发波,都是比较容易配置和实现的。
- 🌟ATmega8 和ATmega328PB自带的模拟比较器功能又和ADC功能相关寄存器共生,不能同时启用,导致需要功能切换时,需要及时改变相关寄存器位的配置。
📑基于反电动势检测过零点有两种方式:
-
🥕基于 ADC 采样的无感方波电机控制 ADC 采样检测过零点。
-
🥕基于比较器检测的无感方波电机控制 比较器检测过零点。(本项目采用的方式)
-
- 🍁比较器检测过零点电路部分:
- 📐理论计算和推导部分:
📙移植到ATmega328PB,相关寄存器变更
- 🌿外部时钟频率原来的8MHz换成16MHz。
- 🌿
TCCR2
对应ATmega328PB相关寄存器TCCR0A
、TCCR0B
- 🌿
SFIOR
对应ATmega328PB相关寄存器ADCSRB
- 🌿参考电压差异,ATmega8 内部参考电压:2.56v,而ATmega328PB内部参考电压:1.1V。(个人移植使用的是AVCC作为参考电压即5V)
- ✨在移植程序中需要处理的个别寄存器位差异,需要调整的地方比较多,如果自己移植,哪里报错有问题改哪里。
🛠3路PWM控制上桥臂说明
- 🌿3路PWM控制上桥臂的信号频率个人采用的是15.625KHz.
- 🌿引脚分别是:PB1、PB2、PB3
- 🌿定时器通道选择和使用:
PB1 OC1A(定时器1,通道A) Mode: Normal top=0xFF
PB2 OC1B(定时器1,通道B) Mode: Normal top=0xFF
PB3 OC2A(定时器2,通道A) Mode: Normal top=0xFF
- 🔧3路控制PWM信号核心代码:
#define PWM_A_ON {TCCR1A=(1<<WGM10);TCCR1B=(1<<WGM12)| (1<<CS12) |(1<<CS10);TCCR2A=(1<<COM2A1)|(1<<WGM21) | (1<<WGM20);TCCR2B=(1<<CS22) | (1<<CS21) | (1<<CS20); DDRB = 0x08;}//PB3
#define PWM_B_ON { TCCR1A = (1<<COM1B1) | (1<<WGM10);TCCR1B= (1<<WGM12)|(1<<CS12)|(1<<CS10);TCCR2A= (1<<WGM21) | (1<<WGM20) ;TCCR2B=(1<<CS22) | (1<<CS21) | (1<<CS20); DDRB = (1<<DDB2);}//PB2
#define PWM_C_ON {TCCR1A = (1<<COM1A1)|(1<<WGM10);TCCR1B=(1<<WGM12) | (1<<CS12)|(1<<CS10);TCCR2A =(1<<WGM21)|(1<<WGM20);TCCR2B= (1<<CS22) | (1<<CS21) | (1<<CS20); DDRB = (1<<DDB1);}//PB1
- 🔑双路同时输出方式:
#define PWM_C_ON {TCCR1A = (1<<COM1A1)|(1<<WGM10);TCCR1B=(1<<CS12);TCCR1C=(1<<FOC1A)|(1<<FOC1B);TCCR2A= (1<<WGM21)|(1<<WGM20);TCCR2B =(1<<FOC2A)|(1<<CS21);DDRB = 0x0A;}//PB3 ->OC2A ;PB1 ->OC1A clkI/O/8
#define PWM_B_ON {TCCR1A = (1<<COM1B1)|(1<<WGM10);TCCR1B=(1<<CS12);TCCR1C=(1<<FOC1A)|(1<<FOC1B); TCCR2A=(1<<COM2A1)|(1<<WGM21)|(1<<WGM20);TCCR2B =(1<<FOC2A)|(1<<CS21);DDRB = 0x0C;}//PB3 ->OC2A ; PB2 ->OC1B
#define PWM_A_ON {TCCR1A =(1<<WGM10);TCCR1B=(1<<CS12);TCCR1C=(1<<FOC1A)|(1<<FOC1B);TCCR2A= (1<<COM2A1)|(1<<WGM21)|(1<<WGM20);TCCR2B = (1<<FOC2A)|(1<<CS21);DDRB = 0x08;}//PB3 ->OC2A
#define PWM_OFF {TCCR1B &=~((1<<CS10)|(1<<CS11)|(1<<CS12));TCCR2B &=~((1<<CS20)|(1<<CS21)|(1<<CS22));PORTB &= ~0x0E;}
双路输出模式下,
PWM_B_ON
,PWM_A_ON
,寄存器TCCR2A
可以都配置定时器2的通道 A 的比较输出模式(1<<COM2A1)
,或者两者配置其一。推荐这两项控制,仅配置PWM_C_ON
,其它参数不变的情况下,测试明显电流要小一些。
📘3路下桥臂采用IO开关控制
#define STEUER_A_L {PORTD &= ~0x30; PORTD |= 0x08;}//U- ->PD3
#define STEUER_B_L {PORTD &= ~0x28; PORTD |= 0x10;}//V- ->PD4
#define STEUER_C_L {PORTD &= ~0x18; PORTD |= 0x20;}//W- ->PD5
- 🔖以下内容待写……
📙ADC检测
- 🔖ADC采集电流,是通过硬件蛇形布线实现的。具体看开源资料中的PCB文件。
- 📑ADC采集代码实现,进行ADC功能前,需要关闭模拟比较器,采集完ADC数据后,需要打开模拟比较器。两个不能同时启用。
//############################################################################
//Strom Analogwerte lesen
unsigned int MessAD(unsigned char channel)
//############################################################################
{
unsigned char sense;
sense = ADMUX; // Sense-Kanal merken
channel |= IntRef;
ADMUX = channel; // 开启对应ADC通道
//SFIOR = 0x00; // Analog Comperator aus
ADCSRB &= ~(1<<ACME);//关闭模拟比较器
// GTCCR &=~((1<<PSRSYNC)|(1<<PSRASY));//将定时器2,0,1,3,4分频系数reset
// MCUCR &=~(1<<PUD);//禁用上拉电阻
// ADC is enabled
PRR0&= ~(1<<PRADC);
ADMUX = channel; //
/*
* initialize ADC:
* Prescaler = 16 -> 1MHz,
* Enable + Start
*/
ADCSRA = (1 << ADEN) | (1 << ADSC) | (1 << ADPS2);
//等待转换完成
while(ADCSRA & (0x01 << ADIF)); /* check if ADC conversion complete */
// while (ADCSRA & (1 << ADSC));// wait for ADC to finish
ADCSRA = 0x00;
ADMUX = sense; // zurück auf den Sense-Kanal
// SFIOR = 0x08; // Analog Comperator ein
ADCSRB |= (1<<ACME);//打开模拟比较器
// ADC is disabled to preserve power
// PRR0|= 1<<PRADC;
ADCSRB |= (1<<ACME);//当ACME为1,且ADEN为0时可以选择ADC相关引脚为负输入
ADCSRA &= ~_BV(ADEN);
/*
Bit 3 – ACIE Analog Comparator Interrupt Enable
Bit 6 – ACBG Analog Comparator Bandgap Select
*/
ACSR |= _BV(ACIE);
ACSR &= ~_BV(ACBG);//当该位被清除时,AIN0被应用于模拟比较器的正输入。
// GTCCR &=~((1<<PSRSYNC)|(1<<PSRASY));//将定时器2,0,1,3,4分频系数reset
MCUCR &=~(1<<PUD);//禁用上拉电阻
return (ADCW);
}
📗三相反电动势检测比较器实现。
- 📄三相反电动势检测,对应电机转动过程就的:消磁事件,过零事件,换相事件。(下面参考图来源于网络)
🧲电机启动实现方式
- 📝强制启动,代码实现:
//############################################################################
//电机启动
char Anwerfen(unsigned char pwm)
//############################################################################
{
unsigned long timer = 200, i;//timer = 300
DISABLE_SENSE_INT;//关闭模拟比较器中断
PWM = 16;
SetPWM();//T1和T2定时计数器赋值,设置pwm占空比
/*补充注释:
开环顺序换向算法,注意换向时必须同步修改比较器端口及触发沿
以便在反相感生电动势到达切换条件时,自动切换到闭环运转状态
*/
Manuell();//换相操作
Delay_ms(200);
/*
MinUpmPulse = SetDelay(300);
while (!CheckDelay(MinUpmPulse))
{
FastADConvert();
if (Strom > 120)
{
STEUER_OFF; // 因短路而关闭
RotBlink(10);
printf("ADC7 Strom STOP\r\n");
return (0);
}
}
*/
PWM = pwm;
while (1)
{
/*
for (i = 0; i < timer; i++)
{
if (!UebertragungAbgeschlossen) SendUart();
else DatenUebertragung();
// Wait(100); // warten 8/8
Wait(25);//328p 16/64
}
DebugAusgaben();
FastADConvert();
if (Strom > 60)
{
STEUER_OFF; // Abschalten wegen Kurzschluss
RotBlink(10);
return (0);
}
*/
timer -= timer / 15 + 1;
if (timer < 25)
{
if (TEST_MANUELL) timer = 25;
else return (1);
}
Manuell();//BLDC换相
Phase++;
Phase %= 6;
AdConvert();
PWM = pwm;
SetPWM();
if (SENSE)
{
PORTD ^= GRUEN;
}
}
}
//############################################################################
/*
开环顺序换向算法,注意换向时必须同步修改比较器端口及触发沿
以便在反相感生电动势到达切换条件时,自动切换到闭环运转状态
*/
void Manuell(void)//BLDC换相
//############################################################################
{
switch (Phase)
{
case 0:
STEUER_A_H;//U+
STEUER_B_L;//V-
SENSE_C;//比较器选择,通道 ADC2(PC2)作为负输入端
SENSE_RISING_INT;//模拟比较器输出的上升沿产生中断
break;
case 1:
STEUER_A_H;
STEUER_C_L;
SENSE_B;//比较器选择,通道 ADC1(PC1)作为负输入端
SENSE_FALLING_INT;
break;
case 2:
STEUER_B_H;
STEUER_C_L;
SENSE_A;//比较器选择,通道 ADC1(PC0)作为负输入端
SENSE_RISING_INT;
break;
case 3:
STEUER_B_H;
STEUER_A_L;
SENSE_C;
SENSE_FALLING_INT;
break;
case 4:
STEUER_C_H;
STEUER_A_L;
SENSE_B;
SENSE_RISING_INT;
break;
case 5:
STEUER_C_H;
STEUER_B_L;
SENSE_A;
SENSE_FALLING_INT;
break;
}
}
- 🌾在STC 无感BLDC代码中也有类似的启动代码:
/******************* 强制电机启动函数 ***************************/
void StartMotor(void)
{
u16 timer,i;
PIE = 0; NIE = 0; // 关比较器中断, PIE=1: 允许比较器上升沿中断, PIE=0: 禁止上升沿中断. NIE=1: 允许比较器下降沿中断, NIE=0: 禁止下降沿中断.
PWM_Value = D_START_PWM; // 初始占空比, 根据电机特性设置
PWMA_CCR1H = (u8)(PWM_Value/256);
PWMA_CCR1L = (u8)(PWM_Value%256);
PWMA_CCR2H = (u8)(PWM_Value/256);
PWMA_CCR2L = (u8)(PWM_Value%256);
PWMA_CCR3H = (u8)(PWM_Value/256);
PWMA_CCR3L = (u8)(PWM_Value%256);
step = 0; StepMotor(); Delay_n_ms(30); // 初始位置
step = 1; StepMotor(); Delay_n_ms(20); // 初始位置
timer = 232; //200电机启动
while(1)
{
for(i=0; i<timer; i++) delay_us(18); //20根据电机加速特性, 最高转速等等调整启动加速速度
timer -= timer /16;
if(++step >= 6) step = 0;
StepMotor();
if(timer < 40) return;
}
}
void StepMotor(void) // 换相序列函数
{
switch(step)
{
case 0: // AB PWM1, PWM2_L=1
PWMA_ENO = 0x00; PWM1_L=0; PWM3_L=0;
Delay_500ns();
PWMA_ENO = 0x01; // 打开A相的高端PWM
PWM2_L = 1; // 打开B相的低端
CMPEXCFG = 0; // 比较器选择C相反电动势, CMP+输入选择,0->P3.7(EMFC),1->P5.0(EMFA),2->P5.1(EMFB),3->ADCIN
PIE = 0; NIE = 1; // 比较器下降沿中断, PIE=1: 允许比较器上升沿中断, PIE=0: 禁止上升沿中断. NIE=1: 允许比较器下降沿中断, NIE=0: 禁止下降沿中断.
break;
case 1: // AC PWM1, PWM3_L=1
PWMA_ENO = 0x01; PWM1_L=0; PWM2_L=0; // 打开A相的高端PWM
Delay_500ns();
PWM3_L = 1; // 打开C相的低端
CMPEXCFG = 2; // 比较器选择B相反电动势, CMP+输入选择,0->P3.7(EMFC),1->P5.0(EMFA),2->P5.1(EMFB),3->ADCIN
PIE = 1; NIE = 0; // 比较器上升沿中断, PIE=1: 允许比较器上升沿中断, PIE=0: 禁止上升沿中断. NIE=1: 允许比较器下降沿中断, NIE=0: 禁止下降沿中断.
break;
case 2: // BC PWM2, PWM3_L=1
PWMA_ENO = 0x00; PWM1_L=0; PWM2_L=0;
Delay_500ns();
PWMA_ENO = 0x04; // 打开B相的高端PWM
PWM3_L = 1; // 打开C相的低端
CMPEXCFG = 1; // 比较器选择A相反电动势, CMP+输入选择,0->P3.7(EMFC),1->P5.0(EMFA),2->P5.1(EMFB),3->ADCIN
PIE = 0; NIE = 1; // 比较器下降沿中断, PIE=1: 允许比较器上升沿中断, PIE=0: 禁止上升沿中断. NIE=1: 允许比较器下降沿中断, NIE=0: 禁止下降沿中断.
break;
case 3: // BA PWM2, PWM1_L=1
PWMA_ENO = 0x04; PWM2_L=0; PWM3_L=0; // 打开B相的高端PWM
Delay_500ns();
PWM1_L = 1; // 打开C相的低端
CMPEXCFG = 0; // 比较器选择C相反电动势, CMP+输入选择,0->P3.7(EMFC),1->P5.0(EMFA),2->P5.1(EMFB),3->ADCIN
PIE = 1; NIE = 0; // 比较器上升沿中断, PIE=1: 允许比较器上升沿中断, PIE=0: 禁止上升沿中断. NIE=1: 允许比较器下降沿中断, NIE=0: 禁止下降沿中断.
break;
case 4: // CA PWM3, PWM1_L=1
PWMA_ENO = 0x00; PWM2_L=0; PWM3_L=0;
Delay_500ns();
PWMA_ENO = 0x10; // 打开C相的高端PWM
PWM1_L = 1; // 打开A相的低端
CMPEXCFG = 2; // 比较器选择B相反电动势, CMP+输入选择,0->P3.7(EMFC),1->P5.0(EMFA),2->P5.1(EMFB),3->ADCIN
PIE = 0; NIE = 1; // 比较器下降沿中断, PIE=1: 允许比较器上升沿中断, PIE=0: 禁止上升沿中断. NIE=1: 允许比较器下降沿中断, NIE=0: 禁止下降沿中断.
break;
case 5: // CB PWM3, PWM2_L=1
PWMA_ENO = 0x10; PWM1_L=0; PWM3_L=0; // 打开C相的高端PWM
Delay_500ns();
PWM2_L = 1; // 打开B相的低端
CMPEXCFG = 1; // 比较器选择A相反电动势, CMP+输入选择,0->P3.7(EMFC),1->P5.0(EMFA),2->P5.1(EMFB),3->ADCIN
PIE = 1; NIE = 0; // 比较器上升沿中断, PIE=1: 允许比较器上升沿中断, PIE=0: 禁止上升沿中断. NIE=1: 允许比较器下降沿中断, NIE=0: 禁止下降沿中断.
break;
default:
break;
}
if(B_start) // 启动时禁止下降沿和上升沿中断
{
CMPIF = 0; //清除比较器中断标志
PIE = 0; NIE = 0; // 比较器上升沿中断, PIE=1: 允许比较器上升沿中断, PIE=0: 禁止上升沿中断. NIE=1: 允许比较器下降沿中断, NIE=0: 禁止下降沿中断.
}
}
📓比较器中断代码
✨用于确定下一刻,应该换哪一相;以及设定下一次比较器触发电平模式。
//############################################################################
//
SIGNAL(TIMER2_COMPB_vect) //SIG_OVERFLOW2 定时器2溢出
//############################################################################
{
}
//############################################################################
//SIG_COMPARATOR模拟比较器
// + Wird durch den Analogkomperator ausgelöst
// + Dadurch wird das Kommutieren erzeugt
SIGNAL(ANALOG_COMP_vect)
//############################################################################
{
unsigned char sense = 0;
do
{
if (SENSE_H) sense = 1;
else sense = 0;
switch (Phase)
{
case 0:
STEUER_A_H;
if (sense)
{
STEUER_C_L;
if (ZeitZumAdWandeln) AdConvert();
SENSE_FALLING_INT;
SENSE_B;
Phase++;
CntKommutierungen++;
}
else
{
STEUER_B_L;
}
break;
case 1:
STEUER_C_L;
if (!sense)
{
STEUER_B_H;
if (ZeitZumAdWandeln) AdConvert();
SENSE_A;
SENSE_RISING_INT;
Phase++;
CntKommutierungen++;
}
else
{
STEUER_A_H;
}
break;
case 2:
STEUER_B_H;
if (sense)
{
STEUER_A_L;
if (ZeitZumAdWandeln) AdConvert();
SENSE_C;
SENSE_FALLING_INT;
Phase++;
CntKommutierungen++;
}
else
{
STEUER_C_L;
}
break;
case 3:
STEUER_A_L;
if (!sense)
{
STEUER_C_H;
if (ZeitZumAdWandeln) AdConvert();
SENSE_B;
SENSE_RISING_INT;
Phase++;
CntKommutierungen++;
}
else
{
STEUER_B_H;
}
break;
case 4:
STEUER_C_H;
if (sense)
{
STEUER_B_L;
if (ZeitZumAdWandeln) AdConvert();
SENSE_A;
SENSE_FALLING_INT;
Phase++;
CntKommutierungen++;
}
else
{
STEUER_A_L;
}
break;
case 5:
STEUER_B_L;
if (!sense)
{
STEUER_A_H;
if (ZeitZumAdWandeln) AdConvert();
SENSE_C;
SENSE_RISING_INT;
Phase = 0;
CntKommutierungen++;
}
else
{
STEUER_C_H;
}
break;
}
}
while ((SENSE_L && sense) || (SENSE_H && !sense));
ZeitZumAdWandeln = 0;
}
🔬移植初版工程源码
链接:https://pan.baidu.com/s/1kCNJCOLu4gqsP1KXwm9d3Q?pwd=6z15
提取码:6z15