基于开源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 merkenchannel |= IntRef;ADMUX = channel; // 开启对应ADC通道//SFIOR = 0x00; // Analog Comperator ausADCSRB &= ~(1<<ACME);//关闭模拟比较器
// GTCCR &=~((1<<PSRSYNC)|(1<<PSRASY));//将定时器2,0,1,3,4分频系数reset
// MCUCR &=~(1<<PUD);//禁用上拉电阻
// ADC is enabledPRR0&= ~(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 finishADCSRA = 0x00;ADMUX = sense; // zurück auf den Sense-Kanal
// SFIOR = 0x08; // Analog Comperator einADCSRB |= (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 EnableBit 6 – ACBG Analog Comparator Bandgap Select*/ACSR |= _BV(ACIE);ACSR &= ~_BV(ACBG);//当该位被清除时,AIN0被应用于模拟比较器的正输入。
// GTCCR &=~((1<<PSRSYNC)|(1<<PSRASY));//将定时器2,0,1,3,4分频系数resetMCUCR &=~(1<<PUD);//禁用上拉电阻return (ADCW);
}
📗三相反电动势检测比较器实现。
- 📄三相反电动势检测,对应电机转动过程就的:消磁事件,过零事件,换相事件。(下面参考图来源于网络)
🧲电机启动实现方式
- 📝强制启动,代码实现:
//############################################################################
//电机启动
char Anwerfen(unsigned char pwm)
//############################################################################
{unsigned long timer = 200, i;//timer = 300DISABLE_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/8Wait(25);//328p 16/64}DebugAusgaben();FastADConvert();if (Strom > 60){STEUER_OFF; // Abschalten wegen KurzschlussRotBlink(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=1PWMA_ENO = 0x00; PWM1_L=0; PWM3_L=0;Delay_500ns();PWMA_ENO = 0x01; // 打开A相的高端PWMPWM2_L = 1; // 打开B相的低端CMPEXCFG = 0; // 比较器选择C相反电动势, CMP+输入选择,0->P3.7(EMFC),1->P5.0(EMFA),2->P5.1(EMFB),3->ADCINPIE = 0; NIE = 1; // 比较器下降沿中断, PIE=1: 允许比较器上升沿中断, PIE=0: 禁止上升沿中断. NIE=1: 允许比较器下降沿中断, NIE=0: 禁止下降沿中断.break;case 1: // AC PWM1, PWM3_L=1PWMA_ENO = 0x01; PWM1_L=0; PWM2_L=0; // 打开A相的高端PWMDelay_500ns();PWM3_L = 1; // 打开C相的低端CMPEXCFG = 2; // 比较器选择B相反电动势, CMP+输入选择,0->P3.7(EMFC),1->P5.0(EMFA),2->P5.1(EMFB),3->ADCINPIE = 1; NIE = 0; // 比较器上升沿中断, PIE=1: 允许比较器上升沿中断, PIE=0: 禁止上升沿中断. NIE=1: 允许比较器下降沿中断, NIE=0: 禁止下降沿中断.break;case 2: // BC PWM2, PWM3_L=1PWMA_ENO = 0x00; PWM1_L=0; PWM2_L=0;Delay_500ns();PWMA_ENO = 0x04; // 打开B相的高端PWMPWM3_L = 1; // 打开C相的低端CMPEXCFG = 1; // 比较器选择A相反电动势, CMP+输入选择,0->P3.7(EMFC),1->P5.0(EMFA),2->P5.1(EMFB),3->ADCINPIE = 0; NIE = 1; // 比较器下降沿中断, PIE=1: 允许比较器上升沿中断, PIE=0: 禁止上升沿中断. NIE=1: 允许比较器下降沿中断, NIE=0: 禁止下降沿中断.break;case 3: // BA PWM2, PWM1_L=1PWMA_ENO = 0x04; PWM2_L=0; PWM3_L=0; // 打开B相的高端PWMDelay_500ns();PWM1_L = 1; // 打开C相的低端CMPEXCFG = 0; // 比较器选择C相反电动势, CMP+输入选择,0->P3.7(EMFC),1->P5.0(EMFA),2->P5.1(EMFB),3->ADCINPIE = 1; NIE = 0; // 比较器上升沿中断, PIE=1: 允许比较器上升沿中断, PIE=0: 禁止上升沿中断. NIE=1: 允许比较器下降沿中断, NIE=0: 禁止下降沿中断.break;case 4: // CA PWM3, PWM1_L=1PWMA_ENO = 0x00; PWM2_L=0; PWM3_L=0;Delay_500ns();PWMA_ENO = 0x10; // 打开C相的高端PWMPWM1_L = 1; // 打开A相的低端CMPEXCFG = 2; // 比较器选择B相反电动势, CMP+输入选择,0->P3.7(EMFC),1->P5.0(EMFA),2->P5.1(EMFB),3->ADCINPIE = 0; NIE = 1; // 比较器下降沿中断, PIE=1: 允许比较器上升沿中断, PIE=0: 禁止上升沿中断. NIE=1: 允许比较器下降沿中断, NIE=0: 禁止下降沿中断.break;case 5: // CB PWM3, PWM2_L=1PWMA_ENO = 0x10; PWM1_L=0; PWM3_L=0; // 打开C相的高端PWMDelay_500ns();PWM2_L = 1; // 打开B相的低端CMPEXCFG = 1; // 比较器选择A相反电动势, CMP+输入选择,0->P3.7(EMFC),1->P5.0(EMFA),2->P5.1(EMFB),3->ADCINPIE = 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