stm32无刷直流电机速度闭环与CAN通讯实验

本次实验通过stm32实现无刷直流电机(BLDC)的速度闭环控制,BLDC磁极位置检测通过三个霍尔传感器(HALL_A,HALL_B,HALL_C)实现。为了便于阅读,代码基本采用库函数。

目录

HALL驱动与编码

转速驱动与计算

电流电压开关管温度检测驱动与计算

BLDC换向与斩波驱动

CAN通讯驱动与发送接收

主函数


HALL驱动与编码

通过三个HALL传感器可以十分简洁的将电机转子的N极确定在60电角度范围内,具体请参考BLDC的HALL换向的相关资料,在此不再赘述。

本次实验通过MCU的PA5,PA6,PA7来检测霍尔信号,并在三个引脚中任意引脚检测到上升沿或下降沿时申请中断(外部中断),进而计算转速(每360电角度进行6次速度计算)。电机霍尔采用5V供电,输出也为0或5V,所以在进入MCU引脚前需通过电阻分压来降压,而信号进入MCU前还需电容滤波以消除干扰。PA5,PA6,PA7的具体配置如下:

void CAP_PIN_INIT(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//配置为上拉输入
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);//GPIOA初始化
	
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource5);
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource6);
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource7);
	
	EXTI_InitTypeDef EXTI_InitStructure;
	EXTI_InitStructure.EXTI_Line = EXTI_Line5|EXTI_Line6|EXTI_Line7;
	EXTI_InitStructure.EXTI_LineCmd = ENABLE;
	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling;//检测双边沿
	EXTI_Init(&EXTI_InitStructure);
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//中断优先级配置
	
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
	NVIC_Init(&NVIC_InitStructure);//配置中断通道
}

通过PA5,PA6,PA7检测到霍尔信号需进行编码,本次实验中PA5对应HALL_A,PA6对应HALL_B,PA7对应HALL_C。通常采用三位二进制来对霍尔信号进行编码,HALL_A为第二位,HALL_B为第一位,HALL_C为第零位。比如当PA5为高电平,PA6为低点平,PA7为高电平时对应的二进制数为101,转为十进制为5。所以编码的过程为:读取PA5当前逻辑(0或1)并左移2位,读取PA6当前逻辑(0或1)并左移1位,读取PA7当前逻辑(0或1),最后相加,具体如下:

uint8_t HALL_trans(void)
{
	uint8_t hall_1,hall_2,hall_3,hall_cal;
	hall_1 = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_5);//读霍尔信号电平(0,1)
	hall_2 = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6);
	hall_3 = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_7);
	hall_cal = (hall_1<<2)+(hall_2<<1)+(hall_3);
	return hall_cal;
}

转速驱动与计算

转速计算需要借助定时器,并在外部中断服务函数中执行。本次实验通过定时器2(通用定时器)来计算转速。定时器2的配置如下:

void TIMER2_INIT(void)//定时器2对捕获事件计时
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
	TIM_InternalClockConfig(TIM2);
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;//时基单元配置
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInitStructure.TIM_Period = 10000-1;
	TIM_TimeBaseInitStructure.TIM_Prescaler = 7200-1;
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
	
	TIM_Cmd(TIM2, DISABLE);//等待软件开定时器2
	
	TIM_ClearFlag(TIM2, TIM_FLAG_Update);
	TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
	NVIC_Init(&NVIC_InitStructure);
}

在检测到HALL引脚上升沿或下降沿时,申请外部中断服务,进入中断服务后先计算转速然后判断转向。对于转速计算,首先通过软件立刻停止定时器计数器,读取当前计数器值,然后换算成事件,而每60电角度进行一次速度计算,电机位4极3槽,所以转速为:50000/count(rpm),该表达式取决于计数器的分频系数,电机的极对数,所以并不唯一,需要大家根据自己情况调整,最后在结束转速计算前对计数器清零,然后再次打开计数器。对于转向的的判断,是通过检测HALL_A上升沿所对应的HALL_B的电平,以及HALL_A下降沿所对应的HALL_B的电平得到的。速度计算具体如下:

int16_t speed_cal(void)
{
	uint16_t count;
	int16_t rpm;
	TIM_Cmd(TIM2, DISABLE);
	count = TIM_GetCounter(TIM2);
    rpm = 50000/count;
	TIM_SetCounter(TIM2, 0);
	TIM_Cmd(TIM2, ENABLE);
	return rpm;
}

外部中断服务函数(调用转速计算,执行转向判断)如下:

void EXTI9_5_IRQHandler(void)//测速中断服务函数
{
	if(EXTI_GetITStatus(EXTI_Line5|EXTI_Line6|EXTI_Line7)==SET)
	{
		rpm_temp = speed_cal();
		num1 = num1+1;
//转向判断
		if(EXTI_GetITStatus(EXTI_Line5)==SET)//A5边沿判断A6电平判断
		{
			num = num+1;
			if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_5) == 1)//判断A5是否为上升沿
			{
				if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6) == 1)
				{
					rpm = rpm_temp;//正转
				}
				else if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6) == 0)
				{
					rpm = -rpm_temp;//反转
				}
			}
			else if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_5) == 0)//判断A5是否为下降沿
			{
				if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6) == 1)
				{
					rpm = -rpm_temp;//反转
				}
				else if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6) == 0)
				{
					rpm = rpm_temp;//正转
				}
			}
		}
		EXTI_ClearITPendingBit(EXTI_Line5);//清中断标志位
		EXTI_ClearITPendingBit(EXTI_Line6);
		EXTI_ClearITPendingBit(EXTI_Line7);
	}
}

定时器2的上溢中断是针对极低转速情况,考虑到目前缺少正交编码器,所以本实验忽略了这种运行状态。

电流电压开关管温度检测驱动与计算

电机的电压电流及开关管温度检测是基于MCU的ADC,其允许0~3.3V连续变化的电压信号输入,并将其编码为0~4095的无符号整型。本次实验采用PC0,PC1,PC4,PC5来做AD采样。其中,PC0,PC1做A,C相电流采样;PC4做开关管温度采样;PC5做输入电压采样。具体如下:

void AD_INIT(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);//开ADC时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
	
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);//ADC时钟6分频(12M)
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;//配置为模拟输入
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_4|GPIO_Pin_5;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOC,&GPIO_InitStructure);
		
	ADC_InitTypeDef ADC_InitSturcture;//独立工作模式
	ADC_InitSturcture.ADC_ContinuousConvMode = DISABLE;//单次触发
	ADC_InitSturcture.ADC_DataAlign = ADC_DataAlign_Right;//转换数据右对齐
	ADC_InitSturcture.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//软件出发AD转换
	ADC_InitSturcture.ADC_Mode = ADC_Mode_Independent;
	ADC_InitSturcture.ADC_NbrOfChannel = 1;//指定通道数量
	ADC_InitSturcture.ADC_ScanConvMode = DISABLE;//关扫描
	ADC_Init(ADC1, &ADC_InitSturcture);//初始化
	
	ADC_Cmd(ADC1, ENABLE);
	
	ADC_ResetCalibration(ADC1);         //校准(四步)
	while(ADC_GetResetCalibrationStatus(ADC1));//等待校准
	ADC_StartCalibration(ADC1);
	while(ADC_GetCalibrationStatus(ADC1));//等待校准
}

电机的电流采样用低端采样方式。为了兼容svpwm,在AC两相桥臂的低端接入采样电阻(如下图所示),阻值为5mΩ。

 图中的栅源电阻(上桥同样)主要起到防静电以及掉电后对栅源电容放电的作用。也可以不加(单脉冲开环换向控制建议去掉该电阻),或替换成阻值更大的电阻,这主要是考虑到上桥自举电容容量。

AC两相采样电阻两端电压送入运放,经放大处理后进入MCU,在进入MCU前建议接滤波电容。本次实验采用采用LM324(4合1)。为了兼容svpwm,需提供一个稳定的偏置电压,取LM324中的一个运放,利用电阻分压与电压跟随的方法,提供稳定的偏置电压,具体如下图:

 根据图中运放接线可知5,10脚电压固定为0.16V,则6,9脚也为0.16V(运放负反馈)。以A相采样为例,设运放输出电压UA_out,A1点输入电压为UA_in,则二者满足:

(UA_out - 0.16)/10k = (0.16 - UA_in)/1k

UA_in = 0.176 - 0.1*UA_out

可以看出这里是反向放大,

IA = UA_in/R = 35.2 - 20*UA_out

特别的,UA_out将送入AD,其电压0~3.3V被编码成0~4095无符号整型,所以电流计算时需对其进行解码,

IA = UA_in/R = 35.2 - 20*UA_out*3.3/4095

为了方便MCU计算,本次实验将电流值扩大100倍(使用整型运算),并用移位代替除法。

IA = UA_in/R = 3520 - (20*UA_out*330)>>12

最后拔掉电机动力线状态下的电流进行检测,得到0偏,并减去(本次实验零偏为60(扩大100倍))。

IA = UA_in/R = 3520 - (20*UA_out*330)>>12 - 60

C相同理。电流计算具体如下:

int16_t ADC_IA(void)//放大100倍
{
	int16_t IA;
	ADC_RegularChannelConfig(ADC1, ADC_Channel_10, 1, ADC_SampleTime_7Cycles5);//规则通道配置
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);//启动
	while(ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC)==RESET);//等待转换完成
	IA = 3520 - ((ADC_GetConversionValue(ADC1)*330)>>12)*20 - 60;//60为零偏(以拔掉动力线AD采样为准)
	if(IA<0)//针对BLDC
	{
		IA = -IA;
	}
	return IA;
}

需要注意的是,对于角接的BLDC仍可采用本次实验的换向策略,但采样电阻采集的电流并不是某一绕组电流。在六状态下,都是某两相串联后与剩下的一相并联,所以当空载反电动势为梯形波,理论上电流较大的一相是采样得到的2/3,而实际则会稍大一些。

电压检测通过直接对输入电压分压实现,温度检测通过定值电阻与热敏电阻对3.3V分压实现,如下图:

 NTC阻值随温度呈指数关系,但MCU计算能力有限,所以本次实验采用查表的方式,将0~100摄氏度间隔5摄氏度,先计算各个温度下NTC的阻值,在计算分压值,最后编码为0~4095的无符号整型,制成一维数组。电压及温度计算具体如下:

int16_t ADC_VBUS(void)//放大100倍
{
	int16_t VBUS;
	ADC_RegularChannelConfig(ADC1, ADC_Channel_15, 1, ADC_SampleTime_7Cycles5);//规则通道配置
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);//启动
	while(ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC)==RESET);//等待转换完成
	VBUS = ((ADC_GetConversionValue(ADC1)*923))>>9;//AD=512(右移9位)对应VDC=9.23V
	return VBUS;
}
int16_t NTC_Table[21]={3048,2848,2683,2473,2271,2096,1873,1686,1515,1345,1197,1056,934,824,730,643,567,500,443,392,347};//NTC查表

int16_t ADC_TEMP(void)
{
	int16_t TEMP;
	uint8_t i;
	uint8_t TEMP_INDEX;
	int16_t AD_value;
	ADC_RegularChannelConfig(ADC1, ADC_Channel_14, 1, ADC_SampleTime_7Cycles5);//规则通道配置
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);//启动
	while(ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC)==RESET);//等待转换完成
	AD_value = ADC_GetConversionValue(ADC1);
	for(i=0;i<=20;i++)
	{
		TEMP_INDEX = i;
		if(AD_value > NTC_Table[i])
		{
			break;
		}
	}
	TEMP = 5*TEMP_INDEX;
	return TEMP;
}

BLDC换向与斩波驱动

本次实验中,BLDC的换向与斩波借助定时器1,为了能够降低开关损耗,本次实验采用上桥臂斩波。PA10,PA9,PA8控制A,B,C相的上桥,PB13,PB14,PB15控制A,B,C相的下桥。驱动芯片为常用的IR2301S。定时器1配置如下:

void TIMER1_INIT(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO,ENABLE);//上桥
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_AFIO,ENABLE);//下桥
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC|RCC_APB2Periph_AFIO,ENABLE);
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//输出PWM需要配置为复用推挽
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10;//打开8 9 10引脚
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);//上桥
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//下桥开关需配置为推挽
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15;//打开13 14 15引脚
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStructure);//下桥
	
	TIM_InternalClockConfig(TIM1);//定时器1启用内部时钟
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;//时基单元配置
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInitStructure.TIM_Period = 100-1;
	TIM_TimeBaseInitStructure.TIM_Prescaler = 72-1;//10k载频
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
	TIM_TimeBaseInit(TIM1, &TIM_TimeBaseInitStructure);
	
	TIM_OCInitTypeDef TIM_OCInitStructure;//开PWM
	TIM_OCStructInit(&TIM_OCInitStructure);//初始化其他未设置的变量
//	TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Reset;
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;//PWM1模式(高有效)
//	TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCNIdleState_Reset;
//	TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_Low;
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
//	TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Disable;//关下路互补,做单(上)桥臂斩波
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
	TIM_OCInitStructure.TIM_Pulse = 0;
	TIM_OC1Init(TIM1, &TIM_OCInitStructure);//(第1通道)
	TIM_OC2Init(TIM1, &TIM_OCInitStructure);//(第2通道)
	TIM_OC3Init(TIM1, &TIM_OCInitStructure);//(第3通道)
	TIM_ClearFlag(TIM1, TIM_FLAG_Update);//清中断标志位
	TIM_ITConfig(TIM1, TIM_IT_Update, ENABLE);//开定时器中断
	
//	TIM_BDTRInitTypeDef TIM_BDTRInitStructure;//配置死区
//	TIM_BDTRInitStructure.TIM_AutomaticOutput = TIM_AutomaticOutput_Enable;
//	TIM_BDTRInitStructure.TIM_Break = TIM_Break_Enable;
//	TIM_BDTRInitStructure.TIM_BreakPolarity = TIM_BreakPolarity_High;
//	TIM_BDTRInitStructure.TIM_DeadTime = 11;
//	TIM_BDTRInitStructure.TIM_LOCKLevel = TIM_LOCKLevel_1;
//	TIM_BDTRInitStructure.TIM_OSSIState = TIM_OSSIState_Enable;
//	TIM_BDTRInitStructure.TIM_OSSRState = TIM_OSSRState_Enable;
//	TIM_BDTRConfig(TIM1, &TIM_BDTRInitStructure);
	TIM_ClearITPendingBit(TIM1, TIM_IT_Update);//清定时器1中断标志位
	TIM_Cmd(TIM1, DISABLE);//开定时器
	TIM_CtrlPWMOutputs(TIM1, ENABLE);//pwm主使能(高级定时器独有)
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//中断优先级配置
	
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = TIM1_UP_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
	NVIC_Init(&NVIC_InitStructure);//配置中断通道
}

特别的,在每次定时器1上溢中断服务中,检测电压电流开关管温度(过欠压过流过热需停机);检测霍尔编码判断磁极位置,安排相应的开关管工作状态;计算速度环PI输出,转换为占空比值送入各通道比较器。所以定时器1的中断优先级最高。

开关管工作状态与霍尔编码值的关系如下:

void CASE5_config(uint16_t pwm_value)//霍尔为5时的斩波服务函数(单桥臂斩波)
{
	TIM_SetCompare3(TIM1, pwm_value);//A相上桥斩波
	TIM_SetCompare2(TIM1, 0);//关B相上桥
	TIM_SetCompare1(TIM1, 0);//关C相上桥
	GPIO_ResetBits(GPIOB, GPIO_Pin_15);//关A相下桥
	GPIO_SetBits(GPIOB, GPIO_Pin_14);//B相下桥打开
	GPIO_ResetBits(GPIOB, GPIO_Pin_13);//关C相下桥
}

void CASE5_INconfig(uint16_t pwm_value)//霍尔为5时的斩波服务函数(单桥臂斩波)反转
{
	TIM_SetCompare1(TIM1, 0);//关A相上桥
	TIM_SetCompare2(TIM1, pwm_value);//B相上桥斩波
	TIM_SetCompare3(TIM1, 0);//关C相上桥
	GPIO_SetBits(GPIOB, GPIO_Pin_15);//开A相下桥
	GPIO_ResetBits(GPIOB, GPIO_Pin_14);//关B相下桥
	GPIO_ResetBits(GPIOB, GPIO_Pin_13);//关C相下桥
}

void CASE4_config(uint16_t pwm_value)//霍尔为4时的斩波服务函数(单桥臂斩波)
{
	TIM_SetCompare3(TIM1, pwm_value);//A相上桥斩波
	TIM_SetCompare2(TIM1, 0);//关B相上桥
	TIM_SetCompare1(TIM1, 0);//关C相上桥
	GPIO_ResetBits(GPIOB, GPIO_Pin_15);//关A相下桥
	GPIO_ResetBits(GPIOB, GPIO_Pin_14);//关B相下桥
	GPIO_SetBits(GPIOB, GPIO_Pin_13);//开C相下桥
}

void CASE4_INconfig(uint16_t pwm_value)//霍尔为4时的斩波服务函数(单桥臂斩波)
{
	TIM_SetCompare3(TIM1, 0);//关A相上桥
	TIM_SetCompare2(TIM1, 0);//关B相上桥
	TIM_SetCompare1(TIM1, pwm_value);//C相上桥斩波
	GPIO_SetBits(GPIOB, GPIO_Pin_15);//开A相下桥
	GPIO_ResetBits(GPIOB, GPIO_Pin_14);//关B相下桥
	GPIO_ResetBits(GPIOB, GPIO_Pin_13);//关C相下桥
}

void CASE6_config(uint16_t pwm_value)//霍尔为6时的斩波服务函数(单桥臂斩波)
{
	TIM_SetCompare1(TIM1, 0);//关A相上桥
	TIM_SetCompare2(TIM1, pwm_value);//B相上桥斩波
	TIM_SetCompare3(TIM1, 0);//关C相上桥
	GPIO_ResetBits(GPIOB, GPIO_Pin_15);//关A相下桥
	GPIO_ResetBits(GPIOB, GPIO_Pin_14);//关B相下桥
	GPIO_SetBits(GPIOB, GPIO_Pin_13);//开C相下桥
}

void CASE6_INconfig(uint16_t pwm_value)//霍尔为6时的斩波服务函数(单桥臂斩波)反转
{
	TIM_SetCompare3(TIM1, 0);//关A相上桥
	TIM_SetCompare2(TIM1, 0);//关B相上桥
	TIM_SetCompare1(TIM1, pwm_value);//C相上桥斩波
	GPIO_ResetBits(GPIOB, GPIO_Pin_15);//关A相下桥
	GPIO_SetBits(GPIOB, GPIO_Pin_14);//开B相下桥
	GPIO_ResetBits(GPIOB, GPIO_Pin_13);//关C相下桥
}

void CASE2_config(uint16_t pwm_value)//霍尔为2时的斩波服务函数(单桥臂斩波)
{
	TIM_SetCompare1(TIM1, 0);//关A相上桥
	TIM_SetCompare2(TIM1, pwm_value);//B相上桥斩波
	TIM_SetCompare3(TIM1, 0);//关C相上桥
	GPIO_SetBits(GPIOB, GPIO_Pin_15);//开A相下桥
	GPIO_ResetBits(GPIOB, GPIO_Pin_14);//关B相下桥
	GPIO_ResetBits(GPIOB, GPIO_Pin_13);//关C相下桥
}

void CASE2_INconfig(uint16_t pwm_value)//霍尔为2时的斩波服务函数(单桥臂斩波)反转
{
	TIM_SetCompare3(TIM1, pwm_value);//关A相上桥
	TIM_SetCompare2(TIM1, 0);//B相上桥斩波
	TIM_SetCompare1(TIM1, 0);//关C相上桥
	GPIO_ResetBits(GPIOB, GPIO_Pin_15);//开A相下桥
	GPIO_SetBits(GPIOB, GPIO_Pin_14);//关B相下桥
	GPIO_ResetBits(GPIOB, GPIO_Pin_13);//关C相下桥
}

void CASE3_config(uint16_t pwm_value)//霍尔为3时的斩波服务函数(单桥臂斩波)
{
	TIM_SetCompare3(TIM1, 0);//关A相上桥
	TIM_SetCompare2(TIM1, 0);//关B相上桥
	TIM_SetCompare1(TIM1, pwm_value);//C相上桥斩波
	GPIO_SetBits(GPIOB, GPIO_Pin_15);//开A相下桥
	GPIO_ResetBits(GPIOB, GPIO_Pin_14);//关B相下桥
	GPIO_ResetBits(GPIOB, GPIO_Pin_13);//关C相下桥
}

void CASE3_INconfig(uint16_t pwm_value)//霍尔为3时的斩波服务函数(单桥臂斩波)反转
{
	TIM_SetCompare3(TIM1, pwm_value);//A相上桥斩波
	TIM_SetCompare2(TIM1, 0);//关B相上桥
	TIM_SetCompare1(TIM1, 0);//关C相上桥
	GPIO_ResetBits(GPIOB, GPIO_Pin_15);//关A相下桥
	GPIO_ResetBits(GPIOB, GPIO_Pin_14);//关B相下桥
	GPIO_SetBits(GPIOB, GPIO_Pin_13);//开C相下桥
}

void CASE1_config(uint16_t pwm_value)//霍尔为1时的斩波服务函数(单桥臂斩波)
{
	TIM_SetCompare3(TIM1, 0);//关A相上桥
	TIM_SetCompare2(TIM1, 0);//关B相上桥
	TIM_SetCompare1(TIM1, pwm_value);//C相上桥斩波
	GPIO_ResetBits(GPIOB, GPIO_Pin_15);//关A相下桥
	GPIO_SetBits(GPIOB, GPIO_Pin_14);//开B相下桥
	GPIO_ResetBits(GPIOB, GPIO_Pin_13);//关C相下桥
}

void CASE1_INconfig(uint16_t pwm_value)//霍尔为1时的斩波服务函数(单桥臂斩波)反转
{
	TIM_SetCompare1(TIM1, 0);//关A相上桥
	TIM_SetCompare2(TIM1, pwm_value);//B相上桥斩波
	TIM_SetCompare3(TIM1, 0);//关C相上桥
	GPIO_ResetBits(GPIOB, GPIO_Pin_15);//关A相下桥
	GPIO_ResetBits(GPIOB, GPIO_Pin_14);//关B相下桥
	GPIO_SetBits(GPIOB, GPIO_Pin_13);//开C相下桥
}

斩波的占空比是速度环PI的输出值:

void BLDC_PWM(uint8_t HALL_CAL,int16_t PI_OUT)
{
	if(PI_OUT>=0)//如果正转
	{
		switch(HALL_CAL)
		{
			case 5:CASE5_config(PI_OUT);break;
			case 4:CASE4_config(PI_OUT);break;
			case 6:CASE6_config(PI_OUT);break;
			case 2:CASE2_config(PI_OUT);break;
			case 3:CASE3_config(PI_OUT);break;
			case 1:CASE1_config(PI_OUT);break;
		}
	}
	else if(PI_OUT<0)//如果反转/制动
	{
		switch(HALL_CAL)
		{
			case 5:CASE5_INconfig(-PI_OUT);break;
			case 4:CASE4_INconfig(-PI_OUT);break;
			case 6:CASE6_INconfig(-PI_OUT);break;
			case 2:CASE2_INconfig(-PI_OUT);break;
			case 3:CASE3_INconfig(-PI_OUT);break;
			case 1:CASE1_INconfig(-PI_OUT);break;
		}
	}	
}

速度环PI计算采用增量式PI:

int16_t PI_CAL(int16_t rpm_tgt, int16_t rpm)
{
	float kp = 0.005;
	float ki = 0.001;	
	static float PI_value;
	int16_t err,PI_out;
	static int16_t err_last;
	
	err = rpm_tgt-rpm;
	PI_value = PI_value+(float)(err-err_last)*kp+(float)err*ki;
	err_last = err;
	
	if(PI_value>100)
	{
		PI_value = 100;
	}
	if(PI_value<-100)
	{
		PI_value = -100;
	}
	PI_out = (int16_t)(PI_value*0.85);
	return PI_out;
}

速度环计算时间相对充裕,本次实验采用浮点型运算,为了保证上桥自举电容有足够的时间充电,设置了0.85的调制比。

过欠压过流过热需停机:

void STOP(void)
{
	GPIO_ResetBits(GPIOA, GPIO_Pin_10);//关A相上桥
	GPIO_ResetBits(GPIOA, GPIO_Pin_9);//关B相上桥
	GPIO_ResetBits(GPIOA, GPIO_Pin_8);//关C相上桥
	GPIO_ResetBits(GPIOB, GPIO_Pin_15);//关A相下桥
	GPIO_ResetBits(GPIOB, GPIO_Pin_14);//关B相下桥
	GPIO_ResetBits(GPIOB, GPIO_Pin_13);//关C相下桥
}

但执行停机前,需将6个引脚全部配置为推挽;从新执行斩波前需将PA10,PA9,PA8重新配置为复用推挽:

void OL_pin_config(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//输出PWM需要配置为复用推挽
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10;//打开8 9 10引脚
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);//上桥
}

void CL_pin_config(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//输出PWM需要配置为推挽
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10;//打开8 9 10引脚
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);//上桥
}

CAN通讯驱动与发送接收

因为缺少其他MCU,本次实验CAN通讯采用回传模式,通过CAN发送目标转速给自己。CAN驱动配置如下:

void CAN_INIT(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	GPIO_InitTypeDef GPIO_InitStructure;//GPIO_InitTypeDef为GPIO_InitStructure的数据类型(结构体)
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//配置结构体成员
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11|GPIO_Pin_12;//打开1,2引脚
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);//GPIOA初始化
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);//CAN配置
	CAN_InitTypeDef CAN_InitStructure;
	CAN_InitStructure.CAN_ABOM = DISABLE;//开自动休眠
	CAN_InitStructure.CAN_AWUM = DISABLE;//开自动唤醒
	CAN_InitStructure.CAN_BS1 = CAN_BS1_5tq;//TBS1长度5Tq
	CAN_InitStructure.CAN_BS2 = CAN_BS2_3tq;//TBS2长度3Tq
	CAN_InitStructure.CAN_Mode = CAN_Mode_LoopBack;//正常工作模式(单机实验下位回传模式)
	CAN_InitStructure.CAN_NART = ENABLE;//禁止重发
	CAN_InitStructure.CAN_Prescaler = 80;//80分频(最终得到10kB速率)
	CAN_InitStructure.CAN_RFLM = DISABLE;//不锁定FIFO
	CAN_InitStructure.CAN_SJW = CAN_SJW_2tq;//最大调整步长2Tq
	CAN_InitStructure.CAN_TTCM = DISABLE;//关闭时间触发
	CAN_InitStructure.CAN_TXFP = DISABLE;//按ID优先级,不按邮箱顺序
	CAN_Init(CAN1, &CAN_InitStructure);
	
	CAN_FilterInitTypeDef CAN_FilterInitStructure;//CAN筛选器配置
	CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;//使能筛选器
	CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_Filter_FIFO0;//启用FIFO0
	CAN_FilterInitStructure.CAN_FilterIdHigh = 0x00;
	CAN_FilterInitStructure.CAN_FilterIdLow = 0x111<<3;//样板ID(低三位不是ID位)
	CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x00;
	CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0xFFF<<3;//只可通过0x111
	CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask;//掩码模式
	CAN_FilterInitStructure.CAN_FilterNumber = 0;//筛选器组0(0-13)
	CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit;//32位长度	
	CAN_FilterInit(&CAN_FilterInitStructure);
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//2位抢占2位响应
	
	NVIC_InitTypeDef NVIC_InitStructure;//CAN中断通道
	NVIC_InitStructure.NVIC_IRQChannel = USB_LP_CAN1_RX0_IRQn;//定义在中容量量产非互联型
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
	NVIC_Init(&NVIC_InitStructure);
	
	CAN_ITConfig(CAN1, CAN_IT_FMP0, ENABLE);//使能接收中断
//	CAN_ClearITPendingBit(CAN1, CAN_IT_FMP0);
}

CAN每次可发送8个8位无符号整形,所以每次发送前需对数据进行编码,将要发送的数据拆分成n个8位无符号整型;而接收后还需进行恢复,这里以对目标转速的编码和解码为例:

uint8_t* CAN_code(int16_t rpm,int16_t VBUS,int16_t IA,int16_t TEMP)//CAN编码
{
	static uint8_t DATA[8] = {7,0xD0,0,0,0,0,0,0};
	DATA[0] = rpm>>8;
	DATA[1] = rpm;
	DATA[2] = VBUS>>8;
	DATA[3] = VBUS;
	DATA[4] = IA>>8;
	DATA[5] = IA;
	DATA[6] = TEMP>>8;
	DATA[7] = TEMP;
	return DATA;
}

int16_t CAN_decode_rpm_tgt(void)
{
	int16_t rpm_tgt;
	uint8_t GET_DATA[8];
	GET_DATA[0] = get_DATA[0];
	GET_DATA[1] = get_DATA[1];
	GET_DATA[2] = get_DATA[2];
	GET_DATA[3] = get_DATA[3];
	GET_DATA[4] = get_DATA[4];
	GET_DATA[5] = get_DATA[5];
	GET_DATA[6] = get_DATA[6];
	GET_DATA[7] = get_DATA[7];
	
	rpm_tgt = (GET_DATA[0]<<8)+GET_DATA[1];
	return rpm_tgt;
}

上述CAN编码函数原本是对检测转速,电压,电流,温度进行编码,然后发送给上位芯片。

CAN发送与CAN接收中断服务函数如下:

void send_CAN(uint32_t STID, uint32_t EXID, uint8_t IDE, uint8_t RTR, uint8_t DLC, uint8_t DATA[8])
{
	CanTxMsg TxMessage;
	TxMessage.StdId = STID;
	TxMessage.ExtId = EXID;
	TxMessage.IDE = IDE;
	TxMessage.RTR = RTR;	
	TxMessage.DLC = DLC;
	
	TxMessage.Data[0] = DATA[0];
	TxMessage.Data[1] = DATA[1];
	TxMessage.Data[2] = DATA[2];
	TxMessage.Data[3] = DATA[3];
	TxMessage.Data[4] = DATA[4];
	TxMessage.Data[5] = DATA[5];
	TxMessage.Data[6] = DATA[6];
	TxMessage.Data[7] = DATA[7];
	
	CAN_Transmit(CAN1, &TxMessage);
}

void USB_LP_CAN1_RX0_IRQHandler()
{
	CanRxMsg RxMessage;//接收数据结构体
	CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);//接收函数
	
	get_DLC = RxMessage.DLC;
	get_EXID = RxMessage.ExtId;
	get_FMI = RxMessage.FMI;
	get_IDE = RxMessage.IDE;
	get_RTR = RxMessage.RTR;
	get_STID = RxMessage.StdId;
	CAN_prob = get_EXID;
	
	get_DATA[0] = RxMessage.Data[0];
	get_DATA[1] = RxMessage.Data[1];
	get_DATA[2] = RxMessage.Data[2];
	get_DATA[3] = RxMessage.Data[3];
	get_DATA[4] = RxMessage.Data[4];
	get_DATA[5] = RxMessage.Data[5];
	get_DATA[6] = RxMessage.Data[6];
	get_DATA[7] = RxMessage.Data[7];
}

主函数

主函数除了执行各驱动初始化外,还在while(1)循环中更新OLED显示,发送CAN数据流,考虑到机械惯性远大于电气,所以将速度环PI计算也放在这里,使其可以被所有中断服务打断,也可以将其放在外部中断服务函数中。主函数具体如下:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "HALL.h"
#include "Timer1.h"
#include "Timer2.h"
#include "BLDC.h"
#include "AD.h"
#include "CAN.h"

uint16_t NO;
uint16_t number;
uint8_t num;
uint8_t num1;
int16_t rpm;
int16_t rpm_temp;
int16_t IA;
int16_t IB;
int16_t TEMP;
int16_t VBUS;
int16_t PI_OUT;
int16_t rpm_tgt;


int main()
{		
	OLED_Init();
	CAP_PIN_INIT();
	AD_INIT();
	CAN_INIT();
	TIMER1_INIT();
	TIMER2_INIT();
	Delay_ms(500);//自举电容充电延时
	TIM_Cmd(TIM1, ENABLE);//开定时器
	
	OL_pin_config();//6状态换向模式
	CL_pin_config();//PWM模式
	
//	OL_START(HALL_trans());	
	while(1)
	{
		rpm_tgt = CAN_decode_rpm_tgt();	
		PI_OUT = PI_CAL(rpm_tgt,rpm);//转速无需过高的响应速度
		TEMP = ADC_TEMP();//温度惯性大,不需较高更新速度,放到主循环可被任意中断打断
		OLED_ShowSignedNum(1, 1, rpm_tgt, 5);
		OLED_ShowSignedNum(2, 1, rpm, 5);
		OLED_ShowSignedNum(3, 1, PI_OUT, 5);
		OLED_ShowSignedNum(4, 1, CURRENT_prob, 5);
		OLED_ShowSignedNum(1, 10, IA, 5);
		OLED_ShowSignedNum(2, 10, IB, 5);
		OLED_ShowSignedNum(3, 10, TEMP, 5);
		OLED_ShowSignedNum(4, 10, VBUS, 5);
		send_CAN(0x00, 
				 0x111, 
				 CAN_Id_Extended, 
				 CAN_RTR_Data, 
				 8, 
				 CAN_code(rpm,VBUS,IA,TEMP));
//		CAN_code(rpm,VBUS,IA,TEMP)
	}
}

编译烧录代码,电机可以较为平稳的旋转,但期间会有较为明显的因斩波引起的电磁噪声,在坎贝尔图上体现为以10k位对称轴左右分布的伞形射线。

WeChat_20230228173133

介于水平的限制,本次实验的算法都是很基础,很普通的,代码也不够简洁。出现的不足之处还请各位指出纠正。

  • 6
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值