GD32103系列 电机驱动项目总结

这次使用的GD系列的芯片来做电机的驱动。然后对使用GD芯片做个总结。这次做的是一个光伏机器人项目。主要用在光伏板上的机器人,通过驱动电机来清理光伏板。

 

1.PWM配置

电机驱动使用的是PWM波来驱动。所以我们首先先用GD芯片来产生PWM波。首先下面是PWM的定时器配置。

void PWM_Timer_Config(uint32_t psr, uint32_t arr, uint32_t duty)
{
    timer_oc_parameter_struct timer_ocintpara;
    timer_parameter_struct timer_initpara;
	timer_break_parameter_struct timer_breakpara;

    rcu_periph_clock_enable(RCU_TIMER0);
		
    timer_deinit(TIMER0);

    /* TIMER0 configuration */
    timer_initpara.prescaler         = psr;
    timer_initpara.alignedmode       = TIMER_COUNTER_EDGE;
    timer_initpara.counterdirection  = TIMER_COUNTER_UP;
    timer_initpara.period            = arr;
    timer_initpara.clockdivision     = TIMER_CKDIV_DIV1;
    timer_initpara.repetitioncounter = 0;
    timer_init(TIMER0,&timer_initpara);

     /* CH0 configuration in PWM mode */
    timer_ocintpara.outputstate  = TIMER_CCX_ENABLE;
    timer_ocintpara.outputnstate = TIMER_CCXN_ENABLE;
    timer_ocintpara.ocpolarity   = TIMER_OC_POLARITY_HIGH;
    timer_ocintpara.ocnpolarity  = TIMER_OCN_POLARITY_HIGH;
    timer_ocintpara.ocidlestate  = TIMER_OC_IDLE_STATE_HIGH;
    timer_ocintpara.ocnidlestate = TIMER_OCN_IDLE_STATE_HIGH;
    timer_channel_output_config(TIMER0,TIMER_CH_0,&timer_ocintpara);

    timer_channel_output_pulse_value_config(TIMER0,TIMER_CH_0,duty);
    timer_channel_output_mode_config(TIMER0,TIMER_CH_0,TIMER_OC_MODE_PWM0);
    timer_channel_output_shadow_config(TIMER0,TIMER_CH_0,TIMER_OC_SHADOW_DISABLE);
		
		
    timer_breakpara.runoffstate      = TIMER_ROS_STATE_DISABLE;
    timer_breakpara.ideloffstate     = TIMER_IOS_STATE_DISABLE ;
    timer_breakpara.deadtime         = 5;
    timer_breakpara.breakpolarity    = TIMER_BREAK_POLARITY_HIGH;
    timer_breakpara.outputautostate  = TIMER_OUTAUTO_ENABLE;
    timer_breakpara.protectmode      = TIMER_CCHP_PROT_0;
    timer_breakpara.breakstate       = TIMER_BREAK_ENABLE;
    timer_break_config(TIMER0,&timer_breakpara);
		//timer_break_enable(TIMER0);
		

    timer_primary_output_config(TIMER0,ENABLE);
    /* auto-reload preload enable */
    timer_auto_reload_shadow_enable(TIMER0);
    timer_enable(TIMER0);
}

从配置中可以算出产生的PWM的频率。时钟是72MHz的,比如设置   PWM_Timer_Config(72 - 1,200 - 1,40);

那么可以算出频率是  72M / 72 * 200 = 5000Hz 周期是200us。设置占空比: TIMER_CH0CV(TIMER0) =  40;     这样设置的占空比就是20%。这个就是往比较寄存器中存入相应的值,就会和PWM周期进行比较,得出相应的占空比。

breakpara是设置死区时间,死区时间只能设置0-255。

 

 

但是电机启动和降速是不能很快的,所以要软启动和软刹车。这样的话PWM频率要慢慢变化。

刚开始想法是每隔一段时间放进去一个比较值,但是发现不行。后来想着每次运行到这就++,但是这样加速时间太慢,而且这和代码整体有关。

最后想着还是先用线性递增的函数来表示,其实如果想用更平滑的启动或者减速,可以用曲线。

uint16_t Calculate_Accelerate_time()
{
	uint16_t cal_time = 0;
	cal_time = (save_p.Run_Speed/100.0) * MOTOR_HIGHEST_SPEED * 50.0;
	cal_time = cal_time - 1000;
	return cal_time;
}



void Motor_Accelerate()
{
	static uint32_t time_count = 0;
	
	if(time_count == 0)
			time_count = tc_GetTimerCount();
	
	else if(tc_GetTimerCount() - time_count <= Calculate_Accelerate_time())
	{
		pwmvalue = 0.02 * (tc_GetTimerCount() - time_count) + MOTOR_INITALSPEED;
		TIMER_CH0CV(TIMER0) =  pwmvalue;
	}
	else
	{
		time_count = 0;
	}
	
	if(pwmvalue == finial_speed)
	{
		TIMER_CH0CV(TIMER0) =  pwmvalue;
		time_count = 0;
		Set_Motor_State(MOTOR_RUN);
	}
}

可以用线性函数来表示,斜率可以自己定。然后可以先试着看看给电机多少占空比才会转,就设置为初始值。因为WEB端是可以设置占空比的,所以接收到的数据解析之后要计算加速时间,不同占空比的加速时间不同。

当电机达到预定的速度之后就匀速运行。减速和这个类似,但有点不同,后面说。

 

2.485通信

这次485虽然是两个口,但是其实是互通的,配置好一个就可以的。

还有就是别忘了要使能接收和发送,不然不能收发数据。其他配置和串口一样。

 

 

3.电机驱动

 

这是这次电机驱动的原理图,使用的是半桥结构,并没有用到常用的H桥结构。左边两路是PWM输入,上面是PWM波,下面是互补的PWM波。右下角是三个继电器来控制电机的正反转和停止的。右上角是电机的电源。

EG2132是电机驱动芯片。

中间两个是NMOS,判断条件是:不论是PMOS还是NMOS,都是P->N,所以下面是P,左边的N。NMOS是给高电平是导通,给低电平时截止。PMOS是刚好相反。

三极管判断和这个类似,右下方是NPN型三极管,给高就会有电流通过。

 

3.1电机正转

假定现在如图所示的电机的正转方向,那么就可以从输入端给占空比缓慢变大的PWM,那么同时互补的PWM则是缓慢变小。当上半桥也就是PWM输入部分为高时,上面的MOS管是导通的,同时下面MOS管截止。此时若控制BRK这个管脚,使其吸合,

那么电机就实现了正转。

 

3.2电机软刹车(动态刹车)

当电机需要刹车时,为了保护电机,不能直接就刹车,而是应该先软刹车,然后再硬刹车。软刹车就是在运行过程中先将速度降下来,然后停止输出PWM波。

 

3.3电机硬刹车(静态刹车)

在上面这个基础上,因为两路PWM都没有输入,将互补的PWM的信号直接拉高,这样中间两个MOS管中间的点就是直接连接到地了。然后关闭控制电机的继电器,然后延时十几ms之后,再将互补的PWM波置为0。

 

3.4 总结

总结一下刹车过程。

  1. 电机减速
  2. 降到很低的速度之后,停止输出PWM波
  3. 将互补通道的信号置为高电平,然后关闭继电器,延时一些时间之后,再置为低电平。
void Motor_Moderate()
{
	static uint32_t speed_low_time = 0;
	
		if(speed_low_time == 0)
		{
			speed_low_time = tc_GetTimerCount();
		}
			
			
			else if(tc_GetTimerCount() - speed_low_time <= Calculate_Stop_time()){
				pwmvalue = -1 * (tc_GetTimerCount() - speed_low_time) + finial_speed;			
				TIMER_CH0CV(TIMER0) =  pwmvalue;
			}
			
			else
			{
				speed_low_time = 0;
			}
			
			if(pwmvalue == MOTOR_INITALSPEED)
			{
					TIMER_CH0CV(TIMER0) =  pwmvalue;
					//breakin½ÅÖÃλ
					gpio_bit_set(GPIOB,GPIO_PIN_11);
					speed_low_time = 0;
						
					//½«#PWMÖÃΪ¸ß							
					gpio_init(GPIOB, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_13);
					gpio_bit_set(GPIOB,GPIO_PIN_13);
				
					Set_Motor_State(MOTOR_HARDBREAK);
			}
}



void Motor_HardBreak()
{
	static uint32_t stop_time =0;
	
	gpio_bit_reset(GPIOB,GPIO_PIN_0);
					
	if(stop_time == 0)
			stop_time = tc_GetTimerCount();
	else if(tc_GetTimerCount() - stop_time > 10)
	{
		gpio_init(GPIOB, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_13);
		gpio_bit_reset(GPIOB,GPIO_PIN_13);
		timer_primary_output_config(TIMER0,DISABLE);
		
		stop_time = 0;
		
		Motor_ifReverse();

	}
	
}

 

4.电流采集(ADC)

在电机运行过程中,需要采集电机运行的电流,这样能保证电机的正常运行。

ADC采集过程中,会采集到数据,这个数值是0-4095之间。这个对应的电压在0-3.3V之间,所以可以通过比例可以得出相应的电压。

将得到的电压除以放大倍数,然后再除以电阻的阻值就是电机的电流。

void rcu_config()
{
	rcu_periph_clock_enable(RCU_GPIOA);
	rcu_periph_clock_enable(RCU_ADC0);
	
	gpio_init(GPIOA, GPIO_MODE_AIN, GPIO_OSPEED_50MHZ, GPIO_PIN_5);
	rcu_adc_clock_config(RCU_CKADC_CKAPB2_DIV8);
}



void adc_config()
{
		rcu_config();
		adc_deinit(ADC0);
		adc_mode_config(ADC_MODE_FREE);

		adc_data_alignment_config(ADC0, ADC_DATAALIGN_RIGHT);

		adc_channel_length_config(ADC0, ADC_INSERTED_CHANNEL, 1);
	
		adc_inserted_channel_config(ADC0, 0, ADC_CHANNEL_5, ADC_SAMPLETIME_55POINT5);
	
		adc_external_trigger_source_config(ADC0,     
        ADC_INSERTED_CHANNEL,ADC0_1_2_EXTTRIG_INSERTED_NONE); 

    
       /* ADC external trigger enable */
       adc_external_trigger_config(ADC0, ADC_INSERTED_CHANNEL, ENABLE);

		adc_flag_clear(ADC0,ADC_FLAG_EOIC);
		
        /* enable ADC interface */
        adc_enable(ADC0);
        delay_us(300);
        /* ADC calibration and reset calibration */
        adc_calibration_enable(ADC0);
}

这次ADC采集是每隔200ms采集一次,使用的是软件中断。是采集11次,然后去掉不符合的电流值,将11个值从小到大排序,去掉最大的和最小的,然后计算平均值。

uint16_t ADC_Filter()
{
	uint8_t i,j;
	uint16_t temp;
	uint16_t sum = 0;
	
	for(i = 0;i < INSERTED_DATA_BUFF_NUM - 1;i++)
	{
		for(j = 0;j < INSERTED_DATA_BUFF_NUM - i;j++)
		{
			if(ADC_inserted_data_buff[j] > ADC_inserted_data_buff[j + 1])
			{
				temp = ADC_inserted_data_buff[j];
				ADC_inserted_data_buff[j] = ADC_inserted_data_buff[j + 1];
				ADC_inserted_data_buff[j + 1] = temp;
			}
		}
	}
	
	for(i = 1; i < INSERTED_DATA_BUFF_NUM;i++)
	{
		sum += ADC_inserted_data_buff[i];
	}
	
	return (sum / (INSERTED_DATA_BUFF_NUM - 2));
}


uint16_t ADCValue_Put_Buf(uint16_t data)
{
	static uint8_t count = 0;
	
	float average_voltage = 0;
	
	if(data > 0x0064)
		ADC_inserted_data_buff[count++] = data;
	
	if(count == INSERTED_DATA_BUFF_NUM)
	{
		average_voltage =  ADC_Filter();
		count = 0;
		memset(ADC_inserted_data_buff,0x00,INSERTED_DATA_BUFF_NUM);
		return average_voltage;
	}
}

ADC的滤波算法还有很多种,具体选择看这个,ADC滤波算法

 

5.其他需要注意点

5.1按键

只要涉及到按键的判断,最好加入防抖动。

uint8_t Judge_Shake()
{
	static uint8_t key_up = 1;
	
	if(key_up && gpio_input_bit_get(GPIOB,GPIO_PIN_3) == RESET)
	{
		key_up = 0;
		JlinkPrintf("LS_GET.......\n");
		delay_ms(15);
		return 1;
	}
	else if(gpio_input_bit_get(GPIOB,GPIO_PIN_3))
		key_up = 1;
		return 0;
}

5.2 Flash读写

嵌入式会经常涉及到Flash的读写,那么Flash的地址一定要选对。最好能从大的往小的选,不然有时候选择的地址会和代码段重复。导致地址里面的数据会被修改。

在map文件中可以看flash和ram的大小。

有一次出现这个情况,我将一些参数存在22k的地方,然后有一段调用某个函数的地方我将它注释了,得出的结果和想象中一致。当打开注释,调用这个函数时候,flash里面的值就被修改了。

当时不知道是什么情况,后来经过师兄指导之后知道,没调用这个函数之前,在map文件中flash用了21.83k,调用这个函数之后,编译之后,flash用了22.08K,说明这里冲突了。

 

 

 

 

 

 

 

 

  • 7
    点赞
  • 49
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
您好!对于GD32系列微控制器驱动步进电机,您可以使用GPIO口来控制步进电机的转动。下面给出一个简单的示例代码,以帮助您开始驱动步进电机: ```C #include "gd32f10x.h" #define MOTOR_PORT GPIOA #define MOTOR_STEP_PIN GPIO_PIN_0 #define MOTOR_DIR_PIN GPIO_PIN_1 void motor_init() { /* 使能GPIOA时钟 */ rcu_periph_clock_enable(RCU_GPIOA); /* 配置MOTOR_STEP_PIN为推挽输出 */ gpio_init(MOTOR_PORT, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, MOTOR_STEP_PIN); /* 配置MOTOR_DIR_PIN为推挽输出 */ gpio_init(MOTOR_PORT, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, MOTOR_DIR_PIN); } void motor_rotate(uint8_t direction, uint32_t steps) { /* 设置方向 */ if (direction == 1) { gpio_bit_reset(MOTOR_PORT, MOTOR_DIR_PIN); // 逆时针方向 } else { gpio_bit_set(MOTOR_PORT, MOTOR_DIR_PIN); // 顺时针方向 } /* 控制步进电机转动 */ for (uint32_t i = 0; i < steps; i++) { gpio_bit_reset(MOTOR_PORT, MOTOR_STEP_PIN); delay_us(500); // 控制脉冲宽度,可根据需要调整 gpio_bit_set(MOTOR_PORT, MOTOR_STEP_PIN); delay_us(500); // 控制脉冲周期,可根据需要调整 } } int main() { motor_init(); while (1) { motor_rotate(0, 200); // 顺时针转动200步 delay_ms(1000); // 停顿1秒 motor_rotate(1, 200); // 逆时针转动200步 delay_ms(1000); // 停顿1秒 } } ``` 上述代码通过控制GPIO口的高低电平实现驱动步进电机的转动,其中`MOTOR_PORT`为步进电机所连接的GPIO端口,`MOTOR_STEP_PIN`为步进电机的步进脉冲引脚,`MOTOR_DIR_PIN`为步进电机的方向控制引脚。`motor_rotate`函数用于控制步进电机的转动方向和步数,其中`direction`为转动方向(0表示顺时针,1表示逆时针),`steps`为转动步数。示例代码中的`main`函数实现了一个简单的循环驱动步进电机旋转的效果。 请注意,示例代码中的延时函数`delay_us`和`delay_ms`需要根据实际的系统时钟频率进行调整或替换,以确保正确的延时时间。此外,您还需要根据实际的硬件连接情况进行适当的修改。希望以上代码能对您有所帮助!如果您还有其他问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值