电机应用-直流有刷电机

目录

直流有刷电机

工作原理

直流有刷减速电机的重要参数

电路原理与分析

驱动芯片分析

L298N驱动芯片

直流有刷减速电机控制实现

控制速度原理

硬件设计

L298N

野火直流有刷电机驱动板-MOS管搭建板

软件设计1:两个直流有刷减速电机按键控制

开发设计

定时器配置

测试环节


直流有刷电机

直流有刷电机具有结构简单、易于控制、成本低等特点。

基本的直流有刷电机在电源和电机间只需要两根电缆,可以节省配线和连接器所需的空间,并降低电缆和连接器的成本。

还可以使用MOSFET/IGBT开关对直流有刷电机进行控制,给电机提供足够好的性能,并且整个电机控制系统也会比较便宜。

直流有刷电机转速快、扭矩小,在某些应用中可能无法满足要求。直流有刷减速电机可以降低转速并提高力矩

工作原理

直流有刷电机在其电枢(转子)上绕有大量的线圈,所产生强大的磁场与外部磁场相互作用产生旋转运动。

电源通过电刷向线框供电,线框就有电流通过,在线框两侧放一对磁极NS形成磁场,磁力线由N到S。

线框有电流时,线框就会收到磁场的作用力,按照左手定则,红色线框受到力F1,蓝色线框受到力F2,F1和F2力的方向相反,使得线框会转动。

当线框转到90°时,换向器改变了线框电流的方向,产生的安培力方向不变,于是线框会连续旋转。

直流有刷减速电机的重要参数

空载转速:正常工作电压下电机不带任何负载的转速(单位为r/min,即转/分)。空载转速由于没有反向力矩,所以输出功率和堵转情况不一样,该参数只是提供一个电机在规定电压下最大转速的参考。

空载电流:正常工作电压下电机不带任何负载的工作电流(单位为mA)。电机越好,该值越小。

负载转速:正常工作电压下电机带负载的转速。

负载力矩:正常工作电压下电机带负载的力矩(单位为N·m,即牛·米)。

负载电流:电机拖动负载时实际检测到的定子电流数值。

堵转力矩:在电机受反向外力使其停止转动时的力矩。如果电机堵转现象经常出现,则会损坏电机或烧坏电机驱动芯片。所以选电机时,除了考虑转速还要考虑堵转。堵转时间一长,电机温度上升的很快,这个值也会下降的很厉害。

堵转电流:在电机受反向外力使其停止转动时的电流。此时电流非常大,稍微就可能会烧毁电机,在实际使用时应尽量避免。

减速比:没有减速齿轮组时转速和有减速齿轮组时转速之比。

功率:一般为额定功率(单位W,即瓦),即在额定电压下能够长期正常运转的最大功率,也是指电动机在制造厂所规定的额定情况下运行时其输出端的机械功率。

电路原理与分析

当拥有一个直流电机和一节电池,只要把电机的两根电源线和电池的电源线连接在一起时,电机就可以正常旋转。反接时电机就反向旋转。但实际单片机一般是用H桥电路来驱动电机。

如下图:H桥电机驱动电路包括4个三极管和一个电机。要想电机运转就必须导通对角线上的一对三极管,所以电流可能会从左到右或从右到左流过电机,从而控制电机转向。

当同一侧的Q1和Q2导通时,电流将从电源经过Q1和Q2,然后直接流到电源负极,这个回路中除了三极管外没有经过负载(电机),这时电流可能会达到最大值而造成烧毁三极管。当同一侧的Q3和Q4导通时也一样。于是可以改进电路原理图。

改进后的电路增加了4个与门和2个非门,以及Q1和Q3换成了NPN三极管。这样的组合可以实现一个信号控制两个同一侧的三极管,并且可以保证在同一侧的两个三极管不会同时导通,即在同一时刻只有一个三极管是导通的。

ENABLE脚接入高电平,IN1脚接入高电平,AND1_2脚是低电平,所以AND1_3脚是低电平,所以Q1截止。而AND2_1脚和AND2_2脚都是高电平,所以ADN2_3脚是高电平,所以Q2导通。然后IN2脚接入低电平,AND4_2脚是高电平,且AND4_1是高电平,所以AND4_3脚是高电平,所以Q3导通。而AND3_1脚是高电平,AND3_2脚是低电平,所以AND3_3脚是低电平,所以Q4截止。故电机逆时针转动。

ENABLE脚接入高电平,IN1脚接入低电平,AND1_2脚是高电平,所以AND1_3脚是高电平,所以Q1导通。而AND2_1脚是高电平,AND2_2脚是低电平,所以ADN2_3脚是低电平,所以Q2截止。然后IN2脚接入高电平,AND4_2脚是低电平,AND4_1是高电平,所以AND4_3脚是低电平,所以Q3截止。而AND3_1脚是高电平,AND3_2脚是高电平,所以AND3_3脚是高电平,所以Q4导通。故电机顺时针转动。

当ENABLE脚接入高电平,IN1和IN2都接入同一电平(高电平或低电平)都只会同时导通上面或下面的两个三极管,不会出现同一侧的三极管同时导通的情况。此时电机停止,当然,ENABLE脚接入低电平时不管IN1、IN2怎样电平,都停止电机。

驱动芯片分析

通常在驱动电机时会选择集成H桥的IC,因为H桥使用分立元件搭建比较麻烦,增加了硬件设计难度。

当我们选择集成IC时,需要考虑集成IC是否能满足电机的驱动电压要求,是否能承受电机工作时的电流等情况。

如果集成IC无法满足功率要求时还是需要自己使用MOS管、三极管等元件搭建H桥电路,这样的分立元件搭建的H桥一般驱动能力也会比集成IC要高。

L298N驱动芯片

L298N是ST公司的产品,内部包含4通道逻辑驱动电路,是一种二相和四相电机的专门驱动芯片,即内含两个H桥的高电压大电流双桥式驱动器,接收标准的TTL逻辑电平信号,可驱动4.5V~46V、2A以下的电机,电流峰值输出可达3A。

原理和刚刚的电路原理分析一致。

直流有刷减速电机控制实现

控制速度原理

PWM通过一定的频率来改变通电和断电的时间,从而控制电路输出功率,在电机的控制周期中,通电时间决定了它的转速。

占空比 = 通电时间 / (通电时间+断电时间),即高电平占整个周期的百分比。

假如T1为高电平时间,T2为低电平时间,T为周期,D为占空比,则D = T1 / T。设电机速度为V,最大速度为Vmax,则V = Vmax * D。当D的大小改变时,速度V也会改变,所以只要改变占空比就能达到控制电机的速度。

硬件设计

主控有刷电机接口如上图,有刷电机接口和无刷电机接口都使用同一个接口。本实验只用到了TIM1_CH1和TIM1_CH2,即PA8和PA9来输出PWM信号控制电机,需注意主控板需要和电机驱动板共地。

L298N

驱动板可以支持12~46V的宽电压供电,并且带输入电压转5V的电压芯片,所以驱动板只需一个电源输入,具体需要多大电压需要根据电机来选择。

ENABLEA和ENABLEB都是使能输入脚,ENABLEA用于控制A桥,ENABLEB用于控制B桥,可以接到单片机的引脚进行电平控制,也可以直接使用跳帽接入5V。

INPUT1和INPUT2是A桥的控制信号,INPUT3和INPUT4是B桥的控制信号,可以接PWM控制电机。

OUTPUT1和OUTPUT2是A桥的输出信号,OUTPUT3和OUTPUT4是B桥的输出信号。

两路电机接口的8个二极管用于防止电机的反电动势损坏L298N。

当E点反电动势为正,超过电源+0.7V时,上端二极管导通,因此输出线就被限位在电源电压+0.7V上,不会超过这个数值。

当E点反电动势为负,低于电源-0.7V时,下端二极管导通,因此输出线就被限位在电源电压-0.7V上,不会低于这个数值。

野火直流有刷电机驱动板-MOS管搭建板

野火直流有刷电机驱动板是使用MOS管搭建的大功率H桥电机驱动板。驱动板可支持12V~70V的宽电压输入,10A过电流保护电路(超过10A可自动禁用电机控制信号),最高功率支持700W。实际使用输入电压需要根据电机进行选择,同时还具有电流采样电路、编码器接口和电源电压检测电路等。

野火使用MOS管搭建的直流有刷电机驱动板做到了信号完全隔离,其它驱动板基本都只是使用光耦隔离了控制信号,并没有对ADC采样电路进行隔离。野火不仅使用光耦对控制信号进行了隔离,还使用AMC1200SDUBR隔离运放对ADC采样电路进行了隔离。

PWM控制信号使用TLP2362高速光耦进行了隔离,SD控制信号使用EL357N光耦进行了隔离。如下图。

下图是使用MOS管搭建的H桥电路,使用两个EG2104驱动四个MOS管。

EG2104S主要功能有逻辑信号输入处理、死区时间控制、电平转换功能、悬浮自举电源结构和上下桥图腾柱式输出。逻辑信号输入端高电平阈值为2.5V以上,低电平阈值为1.0V以下,要求逻辑信号的输出电流小,可以使MCU输出逻辑信号直接连接到EG2104S的输入通道上。EG2104S芯片有一个SHUTDOWN引脚,逻辑输入控制信号低电平有效,强行控制下功率管LO、上功率管HO输出低电平,这样可以直接使用这个引脚做软件控制电机的旋转和停止,还可以实现硬件的限流和保护。

EG2104S内部集成了死区时间控制电路,死区时间波形如下图,其中死区时间DT的典型值为640ns。EG2104S采用自举悬浮驱动电源结构大大简化了驱动电源设计,只用一路电源电压VCC即可完成高端N沟道MOS管和低端N沟道MOS管两个功率开关器件的驱动,给实际应用带来了极大的方便。EG2104S自举电路结构也如下图。RG2104S可以使用外接一个自举二极管和一个自举电容自动完成自举升压功能,假如在下管开通、上管关断期间VC自举电容已充到足够的电压(Vc=VCC),当HO输出高电平时上管开通、下管关断时,VC自举电容上的电压将等效一个电压源作为内部驱动器VB和VS的电源,完成高端N沟道MOS管的驱动。

软件设计1:两个直流有刷减速电机按键控制

以STM32F4为例。

开发设计

按键1:电机1速度加(总10个级别)。

按键2:电机1速度减(总10个级别)。

按键3:电机2速度加(总10个级别)。

按键4:电机2速度减(总10个级别)。

按键5:电机1、电机2同向且都转化方向。

两电机开始状态为停止。

定时器配置

TIM_HandleTypeDef htim1;
TIM_HandleTypeDef htim8;

void MX_TIM1_Init(void)
{
    TIM_ClockConfigTypeDef sClockSourceConfig = {0};
    TIM_MasterConfigTypeDef sMasterConfig = {0};
    TIM_OC_InitTypeDef sConfigOC = {0};
    TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};

    htim1.Instance 					= TIM1;
    htim1.Init.Prescaler 			= 1;
    htim1.Init.CounterMode 			= TIM_COUNTERMODE_UP;
    htim1.Init.Period 				= 5599;
    htim1.Init.ClockDivision 		= TIM_CLOCKDIVISION_DIV1;
    htim1.Init.RepetitionCounter 	= 0;
    htim1.Init.AutoReloadPreload 	= TIM_AUTORELOAD_PRELOAD_ENABLE;
    if (HAL_TIM_Base_Init(&htim1) != HAL_OK)
    {
        Error_Handler();
    }

    sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
    if (HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) != HAL_OK)
    {
        Error_Handler();
    }

    if (HAL_TIM_PWM_Init(&htim1) != HAL_OK)
    {
        Error_Handler();
    }

    sMasterConfig.MasterOutputTrigger 	= TIM_TRGO_RESET;
    sMasterConfig.MasterSlaveMode 		= TIM_MASTERSLAVEMODE_DISABLE;
    if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK)
    {
        Error_Handler();
    }

    sConfigOC.OCMode 		= TIM_OCMODE_PWM1;
    sConfigOC.Pulse 		= 0;
    sConfigOC.OCPolarity 	= TIM_OCPOLARITY_HIGH;
    sConfigOC.OCNPolarity 	= TIM_OCNPOLARITY_HIGH;
    sConfigOC.OCFastMode 	= TIM_OCFAST_DISABLE;
    sConfigOC.OCIdleState 	= TIM_OCIDLESTATE_SET;
    sConfigOC.OCNIdleState 	= TIM_OCNIDLESTATE_RESET;
    if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)	// 配置PWM通道
    {
        Error_Handler();
    }

    if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_2) != HAL_OK)	// 配置PWM通道
    {
        Error_Handler();
    }

    sBreakDeadTimeConfig.OffStateRunMode 	= TIM_OSSR_DISABLE;
    sBreakDeadTimeConfig.OffStateIDLEMode 	= TIM_OSSI_DISABLE;
    sBreakDeadTimeConfig.LockLevel 			= TIM_LOCKLEVEL_OFF;
    sBreakDeadTimeConfig.DeadTime 			= 0;
    sBreakDeadTimeConfig.BreakState 		= TIM_BREAK_DISABLE;
    sBreakDeadTimeConfig.BreakPolarity 		= TIM_BREAKPOLARITY_HIGH;
    sBreakDeadTimeConfig.AutomaticOutput 	= TIM_AUTOMATICOUTPUT_DISABLE;
    if (HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig) != HAL_OK)
    {
        Error_Handler();
    }

    HAL_TIM_MspPostInit(&htim1);
	
	/*开始输出PWM*/
    HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
	HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_2);
}

void MX_TIM8_Init(void)
{
    TIM_ClockConfigTypeDef sClockSourceConfig = {0};
    TIM_MasterConfigTypeDef sMasterConfig = {0};
    TIM_OC_InitTypeDef sConfigOC = {0};
    TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};

    htim8.Instance 					= TIM8;
    htim8.Init.Prescaler 			= 1;
    htim8.Init.CounterMode 			= TIM_COUNTERMODE_UP;
    htim8.Init.Period 				= 5599;
    htim8.Init.ClockDivision 		= TIM_CLOCKDIVISION_DIV1;
    htim8.Init.RepetitionCounter 	= 0;
    htim8.Init.AutoReloadPreload 	= TIM_AUTORELOAD_PRELOAD_ENABLE;
    if (HAL_TIM_Base_Init(&htim8) != HAL_OK)
    {
        Error_Handler();
    }

    sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
    if (HAL_TIM_ConfigClockSource(&htim8, &sClockSourceConfig) != HAL_OK)
    {
        Error_Handler();
    }

    if (HAL_TIM_PWM_Init(&htim8) != HAL_OK)
    {
        Error_Handler();
    }

    sMasterConfig.MasterOutputTrigger 	= TIM_TRGO_RESET;
    sMasterConfig.MasterSlaveMode 		= TIM_MASTERSLAVEMODE_DISABLE;
    if (HAL_TIMEx_MasterConfigSynchronization(&htim8, &sMasterConfig) != HAL_OK)
    {
        Error_Handler();
    }

    sConfigOC.OCMode 		= TIM_OCMODE_PWM1;
    sConfigOC.Pulse 		= 0;
    sConfigOC.OCPolarity 	= TIM_OCPOLARITY_HIGH;
    sConfigOC.OCNPolarity 	= TIM_OCNPOLARITY_HIGH;
    sConfigOC.OCFastMode 	= TIM_OCFAST_DISABLE;
    sConfigOC.OCIdleState 	= TIM_OCIDLESTATE_SET;
    sConfigOC.OCNIdleState 	= TIM_OCNIDLESTATE_RESET;
    if (HAL_TIM_PWM_ConfigChannel(&htim8, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)	// 配置PWM通道
    {
        Error_Handler();
    }

    if (HAL_TIM_PWM_ConfigChannel(&htim8, &sConfigOC, TIM_CHANNEL_2) != HAL_OK)	// 配置PWM通道
    {
        Error_Handler();
    }

    sBreakDeadTimeConfig.OffStateRunMode 	= TIM_OSSR_DISABLE;
    sBreakDeadTimeConfig.OffStateIDLEMode 	= TIM_OSSI_DISABLE;
    sBreakDeadTimeConfig.LockLevel 			= TIM_LOCKLEVEL_OFF;
    sBreakDeadTimeConfig.DeadTime 			= 0;
    sBreakDeadTimeConfig.BreakState 		= TIM_BREAK_DISABLE;
    sBreakDeadTimeConfig.BreakPolarity 		= TIM_BREAKPOLARITY_HIGH;
    sBreakDeadTimeConfig.AutomaticOutput 	= TIM_AUTOMATICOUTPUT_DISABLE;
    if (HAL_TIMEx_ConfigBreakDeadTime(&htim8, &sBreakDeadTimeConfig) != HAL_OK)
    {
        Error_Handler();
    }

    HAL_TIM_MspPostInit(&htim8);
	
	/*开始输出PWM*/
    HAL_TIM_PWM_Start(&htim8, TIM_CHANNEL_1);
	HAL_TIM_PWM_Start(&htim8, TIM_CHANNEL_2);
}

void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *tim_baseHandle)
{
    if (tim_baseHandle->Instance == TIM1)
    {
        __HAL_RCC_TIM1_CLK_ENABLE();
    }
    else if (tim_baseHandle->Instance == TIM8)
    {
        __HAL_RCC_TIM8_CLK_ENABLE();
    }
}

void HAL_TIM_MspPostInit(TIM_HandleTypeDef *timHandle)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    if (timHandle->Instance == TIM1)
    {
        __HAL_RCC_GPIOE_CLK_ENABLE();
		
        /**TIM1 GPIO Configuration
        PE9     ------> TIM1_CH1
        PE11     ------> TIM1_CH2
        */
        GPIO_InitStruct.Pin 		= GPIO_PIN_9 | GPIO_PIN_11;
        GPIO_InitStruct.Mode 		= GPIO_MODE_AF_PP;
        GPIO_InitStruct.Pull 		= GPIO_NOPULL;
        GPIO_InitStruct.Speed 		= GPIO_SPEED_FREQ_LOW;
        GPIO_InitStruct.Alternate 	= GPIO_AF1_TIM1;
        HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);
    }
    else if (timHandle->Instance == TIM8)
    {
        __HAL_RCC_GPIOC_CLK_ENABLE();
		
        /**TIM8 GPIO Configuration
        PC6     ------> TIM8_CH1
        PC7     ------> TIM8_CH2
        */
        GPIO_InitStruct.Pin 		= GPIO_PIN_6 | GPIO_PIN_7;
        GPIO_InitStruct.Mode 		= GPIO_MODE_AF_PP;
        GPIO_InitStruct.Pull 		= GPIO_NOPULL;
        GPIO_InitStruct.Speed 		= GPIO_SPEED_FREQ_LOW;
        GPIO_InitStruct.Alternate 	= GPIO_AF3_TIM8;
        HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
    }
}

void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef *tim_baseHandle)
{
    if (tim_baseHandle->Instance == TIM1)
    {
        __HAL_RCC_TIM1_CLK_DISABLE();
    }
    else if (tim_baseHandle->Instance == TIM8)
    {
        __HAL_RCC_TIM8_CLK_DISABLE();
    }
}

测试环节
/**
  * @brief  设置TIM通道的占空比
	* @param  channel		通道	(1,2)
	* @param  compare		占空比
	*	@note 	无
  * @retval 无
  */
void TIM_SetPWM_pulse(TIM_HandleTypeDef *TIM_TimeStructure, uint32_t channel, int compare)
{
    switch (channel)
    {
        case TIM_CHANNEL_1:
            __HAL_TIM_SET_COMPARE(TIM_TimeStructure, TIM_CHANNEL_1, compare);
            break;
        case TIM_CHANNEL_2:
            __HAL_TIM_SET_COMPARE(TIM_TimeStructure, TIM_CHANNEL_2, compare);
            break;
    }
}

void motor_init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;

    __GPIOG_CLK_ENABLE();
    __GPIOE_CLK_ENABLE();

	// ENA--PG12
	GPIO_InitStruct.Pin 	= GPIO_PIN_12;
    GPIO_InitStruct.Mode 	= GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Speed 	= GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOG, &GPIO_InitStruct);

	// ENB--PE6
    GPIO_InitStruct.Pin = GPIO_PIN_6;
    HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);
}

/**
  * @brief  设置电机速度
  * @param  motor: 电机选择(1、2)
  * @param  speed: 速度(占空比)
  * @retval 无
  */
void set_motor_speed(uint8_t motor, uint16_t speed)
{
	if(motor == 1)
	{
		dutyfactor = speed;

		if (direction == MOTOR_FWD)
		{
			TIM_SetPWM_pulse(&htim1, TIM_CHANNEL_1, dutyfactor);	// 设置比较寄存器的值
		}	
		else	
		{	
			TIM_SetPWM_pulse(&htim1, TIM_CHANNEL_2, dutyfactor);	// 设置比较寄存器的值
		}
	}else
	{
		dutyfactor2 = speed;

		if (direction == MOTOR_FWD)
		{
			TIM_SetPWM_pulse(&htim8, TIM_CHANNEL_1, dutyfactor);	// 设置比较寄存器的值
		}	
		else	
		{	
			TIM_SetPWM_pulse(&htim8, TIM_CHANNEL_2, dutyfactor);	// 设置比较寄存器的值
		}
	}
}

/**
  * @brief  设置电机方向
  * @param  motor: 电机选择(1、2)
  * @param  motor: 方向选择(MOTOR_FWD、MOTOR_REV)
  * @retval 无
  */
void set_motor_direction(uint8_t motor, motor_dir_t dir)
{
	if(motor == 1)
	{
		direction = dir;
	
		if (direction == MOTOR_FWD)
		{
			TIM_SetPWM_pulse(&htim1, TIM_CHANNEL_1, dutyfactor);	// 设置比较寄存器的值
			TIM_SetPWM_pulse(&htim1, TIM_CHANNEL_2, 0);				// 设置比较寄存器的值
		}
		else
		{
			TIM_SetPWM_pulse(&htim1, TIM_CHANNEL_1, 0);				// 设置比较寄存器的值
			TIM_SetPWM_pulse(&htim1, TIM_CHANNEL_2, dutyfactor);	// 设置比较寄存器的值
		}
	}else
	{
		direction2 = dir;
	
		if (direction2 == MOTOR_FWD)
		{
			TIM_SetPWM_pulse(&htim8, TIM_CHANNEL_1, dutyfactor);	// 设置比较寄存器的值
			TIM_SetPWM_pulse(&htim8, TIM_CHANNEL_2, 0);			// 设置比较寄存器的值
		}
		else
		{
			TIM_SetPWM_pulse(&htim8, TIM_CHANNEL_1, 0);			// 设置比较寄存器的值
			TIM_SetPWM_pulse(&htim8, TIM_CHANNEL_2, dutyfactor);	// 设置比较寄存器的值
		}
	}
}

/**
  * @brief  使能电机
  * @param  motor: 电机选择(1、2)
  * @retval 无
  */
void set_motor_enable(uint8_t motor)
{
	if(motor == 1)
	{
		HAL_GPIO_WritePin(ENA_GPIO_PORT, ENA_PIN, GPIO_PIN_SET);
		HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);    // 使能 PWM 通道 1
		HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_2);    // 使能 PWM 通道 2
	}else
	{
		HAL_GPIO_WritePin(ENB_GPIO_PORT, ENB_PIN, GPIO_PIN_SET);
	    HAL_TIM_PWM_Start(&htim8, TIM_CHANNEL_1);   // 使能 PWM 通道 1
		HAL_TIM_PWM_Start(&htim8, TIM_CHANNEL_2);   // 使能 PWM 通道 2
	}
}

/**
  * @brief  禁用电机
  * @param  motor: 电机选择(1、2)
  * @retval 无
  */
void set_motor_disable(uint8_t motor)
{
	if(motor == 1)
	{
		HAL_GPIO_WritePin(ENA_GPIO_PORT, ENA_PIN, GPIO_PIN_RESET);
		HAL_TIM_PWM_Stop(&htim1, TIM_CHANNEL_1);    // 禁用 PWM 通道 1
		HAL_TIM_PWM_Stop(&htim1, TIM_CHANNEL_2);    // 禁用 PWM 通道 2
	}else
	{
		HAL_GPIO_WritePin(ENB_GPIO_PORT, ENB_PIN, GPIO_PIN_RESET);
	    HAL_TIM_PWM_Stop(&htim8, TIM_CHANNEL_1);   // 禁用 PWM 通道 1
		HAL_TIM_PWM_Stop(&htim8, TIM_CHANNEL_2);   // 禁用 PWM 通道 2
	}
}

void test(void)
{
	__IO uint16_t ChannelPulse = 0;
    __IO uint16_t ChannelPulse2 = 0;
	uint8_t i = 0;
	
	初始化
	motor_init();
	
	set_motor_enable(1);				// 使能电机1
	set_motor_enable(2);				// 使能电机2
    set_motor_speed(1, ChannelPulse);	// 电机1开始状态为停止
	set_motor_speed(2, ChannelPulse2);	// 电机2开始状态为停止
	
	while (1)
    {
        /* 扫描KEY1 */
        if (Key_Scan(KEY1_GPIO_PORT, KEY1_PIN) == KEY_ON)
        {
            /* 加大占空比,即加快电机1的速度 */
            ChannelPulse += 5600 / 10;

            if (ChannelPulse > 5600)
            {
                ChannelPulse = 5600;
            }

            set_motor_speed(1, ChannelPulse);
        }

        /* 扫描KEY2 */
        if (Key_Scan(KEY2_GPIO_PORT, KEY2_PIN) == KEY_ON)
        {
            if (ChannelPulse < 5600 / 10)
            {
                ChannelPulse = 0;
            }
            else
            {
				/* 减小占空比,即减满电机1的速度 */
                ChannelPulse -= 5600 / 10;
            }

            set_motor_speed(1, ChannelPulse);
        }

        /* 扫描KEY3 */
        if (Key_Scan(KEY3_GPIO_PORT, KEY3_PIN) == KEY_ON)
        {
            /* 加大占空比,即加快电机2的速度 */
            ChannelPulse2 += 5600 / 10;

            if (ChannelPulse2 > 5600)
            {
                ChannelPulse2 = 5600;
            }

            set_motor_speed(2, ChannelPulse2);
        }

        /* 扫描KEY4 */
        if (Key_Scan(KEY4_GPIO_PORT, KEY4_PIN) == KEY_ON)
        {
            if (ChannelPulse2 < 5600 / 10)
            {
                ChannelPulse2 = 0;
            }
            else
            {
				/* 减小占空比,即减满电机1的速度 */
                ChannelPulse2 -= 5600 / 10;
            }

            set_motor_speed(2, ChannelPulse2);
        }

        /* 扫描KEY5 */
        if (Key_Scan(KEY5_GPIO_PORT, KEY5_PIN) == KEY_ON)
        {
            /* 转换方向(两电机同向) */
            set_motor_direction(1, (++i % 2) ? MOTOR_FWD : MOTOR_REV);
            set_motor_direction(2, (i % 2) ? MOTOR_FWD : MOTOR_REV);
        }
    }
}

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/119102.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Android Studio(项目打包成APK)

打包流程 直接上图即可 按照上面操作后&#xff0c;即可以开始打包&#xff0c;一般第一次打包都需要几分钟&#xff08;我第一次打包花了七八分钟&#xff09;&#xff0c;如果打包错误了也别担心&#xff0c;可以查看错误分析一下原因&#xff0c;实在不行可以把错误放到网站…

ElasticSearch 实现 全文检索 支持(PDF、TXT、Word、HTML等文件)通过 ingest-attachment 插件实现 文档的检索

一、Attachment 介绍 Attachment 插件是 Elasticsearch 中的一种插件&#xff0c;允许将各种二进制文件&#xff08;如PDF、Word文档等&#xff09;以及它们的内容索引到 Elasticsearch 中。插件使用 Apache Tika 库来解析和提取二进制文件的内容。通过使用 Attachment 插件&a…

Qt全局定义

一、QtGlobal头文件 头文件中包含了Qt类库的一些全局定义&#xff0c;包括&#xff1a; 基本数据类型全局函数宏定义 二、基本数据类型 三、全局函数 四、宏定义 1.Qt版本相关的宏 1.1 QT_VERSION 这个宏展开为数值形式 0xMMNNPP (MM major, NN minor, PP patch) 表示…

Hadoop知识点全面总结

文章目录 什么是HadoopHadoop发行版介绍Hadoop版本演变历史Hadoop3.x的细节优化Hadoop三大核心组件介绍HDFS体系结构NameNode介绍总结 SecondaryNameNode介绍DataNode介绍DataNode总结 MapReduce介绍分布式计算介绍MapReduce原理剖析MapReduce之Map阶段MapReduce之Reduce阶段 实…

Verilog HDL语言基础知识

目录 Verilog HDL语言基础知识 6.1.2 Verilog HDL模块的结构 6.1.3 逻辑功能定义 6.2.1 常量 6.3 运算符及表达式 6.4.2 条件语句 Verilog HDL语言基础知识 先来看两个Verilog HDL程序。 例6.1 一个8位全加器的 Verilog HDL源代码 module adder8(cout,sum,ina,…

Si4010 一款带有MCU SoC RF发射机芯片 无线遥控器

Si4010是一款完全集成的SoC RF发射机&#xff0c;带有嵌入式CIP-51 8051 MCU&#xff0c;专为1GHz以下ISM频带设计。该芯片针对电池供电的应用进行了优化&#xff0c;工作电压为1.8至3.6 V&#xff0c;待机电流小于10 nA的超低电流消耗。高功率放大器可提供高达10 dBm的输出功率…

pytorch复现_UNet

什么是UNet U-Net由收缩路径和扩张路径组成。收缩路径是一系列卷积层和汇集层&#xff0c;其中要素地图的分辨率逐渐降低。扩展路径是一系列上采样层和卷积层&#xff0c;其中特征地图的分辨率逐渐增加。 在扩展路径中的每一步&#xff0c;来自收缩路径的对应特征地图与当前特征…

前端框架Vue学习 ——(四)Axios

文章目录 Axios 介绍Axios 入门Vue项目中使用 Axios Axios 介绍 介绍: Axios 对原生的 Ajax 进行了封装&#xff0c;简化书写&#xff0c;快速开发。&#xff08;异步请求&#xff09; 官网: https://www.axios-http.cn/ 官网介绍&#xff1a;Axios 是一个基于 promise 网络请…

@Tag和@Operation标签失效问题。SpringDoc 2.2.0(OpenApi 3)和Spring Boot 3.1.1集成

问题 Tag和Operation标签失效 但是Schema标签有效 pom依赖 <!-- 接口文档--><!--引入openapi支持--><dependency><groupId>org.springdoc</groupId><artifactId>springdoc-openapi-starter-webmvc-ui</artifactId><vers…

多测师肖sir_高级金牌讲师_jenkins搭建

jenkins操作手册 一、jenkins介绍 1、持续集成&#xff08;CI&#xff09; Continuous integration 持续集成 团队开发成员每天都有集成他们的工作&#xff0c;通过每个成员每天至少集成一次&#xff0c;也就意味着一天有可 能多次集成。在工作中我们引入持续集成&#xff0c;通…

水利部加快推进小型水库除险加固,大坝安全监测是重点

国务院常务会议明确到2025年前&#xff0c;完成新出现病险水库的除险加固&#xff0c;配套完善重点小型水库雨水情和安全监测设施&#xff0c;实现水库安全鉴定和除险加固常态化。 为加快推进小型水库除险加固前期工作&#xff0c;水利部协调财政部提前下达了2023年度中央补助…

chinese-stable-diffusion中文场景文生图prompt测评集合

腾讯混元大模型文生图操作指南.dochttps://mp.weixin.qq.com/s/u0AGtpwm_LmgnDY7OQhKGg腾讯混元大模型再进化&#xff0c;文生图能力重磅上线&#xff0c;这里是一手实测腾讯混元的文生图在人像真实感、场景真实感上有比较明显的优势&#xff0c;同时&#xff0c;在中国风景、动…

ActiveMq学习⑨__基于zookeeper和LevelDB搭建ActiveMQ集群

引入消息中间件后如何保证其高可用&#xff1f; 基于zookeeper和LevelDB搭建ActiveMQ集群。集群仅提供主备方式的高可用集群功能&#xff0c;避免单点故障。 http://activemq.apache.org/masterslave LevelDB&#xff0c;5.6版本之后推出了LecelDB的持久化引擎&#xff0c;它使…

错误:ERROR Cannot read properties of null (reading ‘type‘)

ERROR Cannot read properties of null (reading ‘type’) TypeError: Cannot read properties of null (reading ‘type’) <template><el-card><el-row :gutter"20" class"header"><el-col :span"7"><el-input pl…

二、Hadoop分布式系统基础架构

1、分布式 分布式体系中&#xff0c;会存在众多服务器&#xff0c;会造成混乱等情况。那如何让众多服务器一起工作&#xff0c;高效且不出现问题呢&#xff1f; 2、调度 &#xff08;1&#xff09;架构 在大数据体系中&#xff0c;分布式的调度主要有2类架构模式&#xff1a…

【Redis】SSM整合Redis注解式缓存的使用

【Redis】SSM整合Redis&注解式缓存的使用 一、SSM整合Redis1.2.配置文件spring-redis.xml1.3.修改applicationContext.xml1.4.配置redis的key生成策略 二、Redis的注解式开发及应用场景2.1.什么是Redis注解式2.实列测试 三、Redis中的击穿、穿透、雪崩的三种场景 一、SSM整…

WebSocket Day03 : SpringMVC整合WebSocket

前言 在现代Web应用程序中&#xff0c;实时性和即时通信变得越来越重要。传统的HTTP请求-响应模式无法满足实时数据传输和双向通信的需求。随着技术的发展&#xff0c;WebSocket成为了一种强大而灵活的解决方案。 WebSocket是HTML5提供的一种新的通信协议&#xff0c;它通过一…

vue.js实现科室无限层选中和回显

一、效果展示&#xff1a; 展示可选层级 查看选中的值 二、实现&#xff1a; <el-form-item label"相关科室:" prop"orgId"><el-cascaderpopper-class"cascader-my":options"orgOptions":show-all-levels"false"…

NVIDIA Jetson SOC 内存分配策略

CPU 是Host, GPU 是Device, 系统内存分配策略如下: 这段话的翻译如下&#xff1a; 集成的GPU会和CPU以及其他Tegra引擎共享DRAM&#xff08;动态随机存储器&#xff09;&#xff0c;并且CPU可以通过将DRAM的内容移动到交换区域&#xff08;SWAP area&#xff09;或者相反来控制…

成功品牌的营销秘诀揭密,营销秘诀,品牌成功

品牌营销是将品牌塑造为消费者心目中有价值的存在&#xff0c;从而提高品牌认知度和价值的过程。品牌营销是任何一家企业成功的关键所在。如果一家企业能够正确地营销其品牌&#xff0c;那么它就能够在行业中发挥更大的作用。接下来&#xff0c;迅推客将深入探讨品牌营销的重要…