基于开源ATmega8 无感BLDC程序移植到ATmega328PB

基于开源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
🎉此项目开源,同类型驱动控制里面,在当时算得上最早被公开的了。里面的部分经典算法一直被同类型驱动上所采用。
  • 📜按照此开源资料,如果想移植到其它单片机上,按照此反电动势检测实现方法,硬件要求是需要有一个模拟比较器。和需要能产生3路PWM信号来驱动H桥,这最基本的电机转动的要求。如果还需要电流采集以及ADC调速,那就需要单片机要有ADC资源。
  • 🌿早期相关ATmega328P相关移植参考文章:https://www.amobbs.com/thread-4652868-1-1.html
  • 🌼移植后基于自制驱动板,驱动转动效果:
  • 📌自制驱动板相关内容《自制无感无刷电机驱动板
  • ✒自己亲手移植一遍工程,不是简单的照搬一次代码,起码对整个无感无刷电机运转实现,有一个认识和了解,同时硬件上学习了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相关寄存器TCCR0ATCCR0B
  • 🌿SFIOR对应ATmega328PB相关寄存器ADCSRB
  • 🌿参考电压差异,ATmega8 内部参考电压:2.56v,而ATmega328PB内部参考电压:1.1V。(个人移植使用的是AVCC作为参考电压即5V)
  • ✨在移植程序中需要处理的个别寄存器位差异,需要调整的地方比较多,如果自己移植,哪里报错有问题改哪里。

🛠3路PWM控制上桥臂说明

  • 🌿3路PWM控制上桥臂的信号频率个人采用的是15.625KHz.
  • 🌿引脚分别是:PB1、PB2、PB3
  • 🌿定时器通道选择和使用:
c 复制代码
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信号核心代码:
c 复制代码
#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
  • 🔑双路同时输出方式:
c 复制代码
#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开关控制

c 复制代码
#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数据后,需要打开模拟比较器。两个不能同时启用。
c 复制代码
//############################################################################
//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);
}

📗三相反电动势检测比较器实现。

  • 📄三相反电动势检测,对应电机转动过程就的:消磁事件,过零事件,换相事件。(下面参考图来源于网络)

🧲电机启动实现方式

  • 📝强制启动,代码实现:
c 复制代码
//############################################################################
//电机启动
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代码中也有类似的启动代码:
c 复制代码
/******************* 强制电机启动函数 ***************************/
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: 禁止下降沿中断.
	}
}

📓比较器中断代码

✨用于确定下一刻,应该换哪一相;以及设定下一次比较器触发电平模式。

c 复制代码
//############################################################################
//
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;
}

🔬移植初版工程源码

c 复制代码
链接:https://pan.baidu.com/s/1kCNJCOLu4gqsP1KXwm9d3Q?pwd=6z15 
提取码:6z15