目录
直流有刷电机
工作原理
直流有刷减速电机的重要参数
电路原理与分析
驱动芯片分析
L298N驱动芯片
直流有刷减速电机控制实现
控制速度原理
硬件设计
L298N
野火直流有刷电机驱动板-MOS管搭建板
软件设计1:两个直流有刷减速电机按键控制
开发设计
定时器配置
测试环节
直流有刷减速电机驱动板电流电压采集
ADC配置
测试环节
直流有刷减速电机驱动板限电流、过电压、欠电压保护
ADC配置
测试环节
直流有刷电机
直流有刷电机具有结构简单、易于控制、成本低等特点。
基本的直流有刷电机在电源和电机间只需要两根电缆,可以节省配线和连接器所需的空间,并降低电缆和连接器的成本。
还可以使用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 ConfigurationPE9 ------> TIM1_CH1PE11 ------> 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 ConfigurationPC6 ------> TIM8_CH1PC7 ------> 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--PG12GPIO_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--PE6GPIO_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 通道 1HAL_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 通道 1HAL_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 通道 1HAL_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 通道 1HAL_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); // 使能电机1set_motor_enable(2); // 使能电机2set_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);}}
}
直流有刷减速电机驱动板电流电压采集
ADC配置
ADC_HandleTypeDef hadc1;
DMA_HandleTypeDef hdma_adc1;void MX_ADC1_Init(void)
{ADC_ChannelConfTypeDef sConfig = {0};/** Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion)*/hadc1.Instance = ADC1;hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;hadc1.Init.Resolution = ADC_RESOLUTION_12B;hadc1.Init.ScanConvMode = ENABLE;hadc1.Init.ContinuousConvMode = ENABLE;hadc1.Init.DiscontinuousConvMode = DISABLE;hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;hadc1.Init.NbrOfConversion = 2;hadc1.Init.DMAContinuousRequests = ENABLE;hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;if (HAL_ADC_Init(&hadc1) != HAL_OK){Error_Handler();}/** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.*/sConfig.Channel = ADC_CHANNEL_9;sConfig.Rank = 1;sConfig.SamplingTime = ADC_SAMPLETIME_3CYCLES;if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK){Error_Handler();}/** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.*/sConfig.Channel = ADC_CHANNEL_8;sConfig.Rank = 2;if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK){Error_Handler();}
}void HAL_ADC_MspInit(ADC_HandleTypeDef *adcHandle)
{GPIO_InitTypeDef GPIO_InitStruct = {0};if (adcHandle->Instance == ADC1){__HAL_RCC_ADC1_CLK_ENABLE();__HAL_RCC_GPIOB_CLK_ENABLE();/**ADC1 GPIO ConfigurationPB0 ------> ADC1_IN8PB1 ------> ADC1_IN9*/GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1;GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;GPIO_InitStruct.Pull = GPIO_NOPULL;HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);/* ADC1 DMA Init *//* ADC1 Init */hdma_adc1.Instance = DMA2_Stream0;hdma_adc1.Init.Channel = DMA_CHANNEL_0;hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY;hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE;hdma_adc1.Init.MemInc = DMA_MINC_ENABLE;hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;hdma_adc1.Init.Mode = DMA_CIRCULAR;hdma_adc1.Init.Priority = DMA_PRIORITY_HIGH;hdma_adc1.Init.FIFOMode = DMA_FIFOMODE_DISABLE;if (HAL_DMA_Init(&hdma_adc1) != HAL_OK){Error_Handler();}__HAL_LINKDMA(adcHandle, DMA_Handle, hdma_adc1);}
}void HAL_ADC_MspDeInit(ADC_HandleTypeDef *adcHandle)
{if (adcHandle->Instance == ADC1){__HAL_RCC_ADC1_CLK_DISABLE();/**ADC1 GPIO ConfigurationPB0 ------> ADC1_IN8PB1 ------> ADC1_IN9*/HAL_GPIO_DeInit(GPIOB, GPIO_PIN_0 | GPIO_PIN_1);/* ADC1 DMA DeInit */HAL_DMA_DeInit(adcHandle->DMA_Handle);}
}void MX_DMA_Init(void)
{/* DMA controller clock enable */__HAL_RCC_DMA2_CLK_ENABLE();/* DMA interrupt init *//* DMA2_Stream0_IRQn interrupt configuration */HAL_NVIC_SetPriority(DMA2_Stream0_IRQn, 0, 0);HAL_NVIC_EnableIRQ(DMA2_Stream0_IRQn);
}
测试环节
#define ADC_NUM_MAX 2048 // ADC 转换结果缓冲区最大值
#define VREF 3.3f // 参考电压,理论上是3.3,可通过实际测量得3.258static uint16_t adc_buff[ADC_NUM_MAX]; // 电压采集缓冲区
static uint16_t vbus_adc_mean = 0; // 电源电压 ACD 采样结果平均值
static uint32_t adc_mean_sum = 0; // 平均值累加
static uint32_t adc_mean_count = 0; // 累加计数/*** @brief 常规转换在非阻塞模式下完成回调* @param hadc: ADC 句柄.* @retval 无*/
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
{uint32_t adc_mean = 0;// 停止 ADC 采样,处理完一次数据在继续采样HAL_ADC_Stop_DMA(hadc); /* 累加ADC通道9的采样值 */for (uint32_t count = 0; count < ADC_NUM_MAX; count += 2){adc_mean += (uint32_t)adc_buff[count];}adc_mean_sum += adc_mean / (ADC_NUM_MAX / 2); // 累加电流平均后累加adc_mean_count++; // 累加计数adc_mean = 0;/* 累加ADC通道8的采样值 */for (uint32_t count = 1; count < ADC_NUM_MAX; count += 2){adc_mean += (uint32_t)adc_buff[count];}vbus_adc_mean = adc_mean / (ADC_NUM_MAX / 2); // 保存平均值HAL_ADC_Start_DMA(hadc, (uint32_t *)&adc_buff, ADC_NUM_MAX); // 开始 ADC 采样
} /*** @brief 获取电流值* @param 无* @retval 转换得到的电流值*/
int32_t get_curr_val(void)
{static uint8_t flag = 0;static uint32_t adc_offset = 0; // 偏置电压int16_t curr_adc_mean = 0; // 电流 ADC 采样结果平均值curr_adc_mean = adc_mean_sum / adc_mean_count; // 保存平均值adc_mean_count = 0;adc_mean_sum = 0;if (flag < 17){adc_offset = curr_adc_mean; // 多次记录偏置电压,待系统稳定偏置电压才为有效值flag += 1;}if (curr_adc_mean >= adc_offset){curr_adc_mean -= adc_offset; // 减去偏置电压}else{curr_adc_mean = 0;}// 获取电压值float vdc = ((float)vbus_adc_mean / 4096.0f * VREF);// 得到电流值,电压放大8倍,0.02是采样电阻,单位mA。电流采样电路得知的return ( (float)vdc / 8.0f / 0.02f * 1000.0f )
}/*** @brief 获取电源电压值* @param 无* @retval 转换得到的电流值*/
float get_vbus_val(void)
{// 获取电压值float vdc = ((float)vbus_adc_mean / 4096.0f * VREF); // 电压最大值(测量电压是电源电压的1/37),电流、电压采样电路得知的return ( ((float)vdc - 1.24f) * 37.0f );
}void test(void)
{uint8_t flag = 0;初始化HAL_ADC_Start_DMA(&hadc1, (uint32_t *)&adc_buff, ADC_NUM_MAX);while(1){if (HAL_GetTick() % 50 == 0 && flag == 0) // 每50毫秒读取一次电流、电压{flag = 1;int32_t current = get_curr_val();printf("电源电压:%.2fV,电流:%dmA\r\n", get_vbus_val(), current);}else if (HAL_GetTick() % 50 != 0 && flag == 1){flag = 0;}}
}
直流有刷减速电机驱动板限电流、过电压、欠电压保护
ADC配置
ADC_HandleTypeDef hadc1;
DMA_HandleTypeDef hdma_adc1;/* ADC1 init function */
void MX_ADC1_Init(void)
{ADC_AnalogWDGConfTypeDef AnalogWDGConfig = {0};ADC_ChannelConfTypeDef sConfig = {0};/** Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion)*/hadc1.Instance = ADC1;hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;hadc1.Init.Resolution = ADC_RESOLUTION_12B;hadc1.Init.ScanConvMode = ENABLE;hadc1.Init.ContinuousConvMode = ENABLE;hadc1.Init.DiscontinuousConvMode = DISABLE;hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;hadc1.Init.NbrOfConversion = 2;hadc1.Init.DMAContinuousRequests = ENABLE;hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;if (HAL_ADC_Init(&hadc1) != HAL_OK){Error_Handler();}/** Configure the analog watchdog*/AnalogWDGConfig.WatchdogMode = ADC_ANALOGWATCHDOG_SINGLE_REG;AnalogWDGConfig.HighThreshold = (15 / 37 + 1.24) / 3.3 * 4096;AnalogWDGConfig.LowThreshold = (10 / 37 + 1.24) / 3.3 * 4096;AnalogWDGConfig.Channel = ADC_CHANNEL_8;AnalogWDGConfig.ITMode = ENABLE;if (HAL_ADC_AnalogWDGConfig(&hadc1, &AnalogWDGConfig) != HAL_OK){Error_Handler();}/** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.*/sConfig.Channel = ADC_CHANNEL_9;sConfig.Rank = 1;sConfig.SamplingTime = ADC_SAMPLETIME_3CYCLES;if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK){Error_Handler();}/** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.*/sConfig.Channel = ADC_CHANNEL_8;sConfig.Rank = 2;if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK){Error_Handler();}
}void MX_DMA_Init(void)
{__HAL_RCC_DMA2_CLK_ENABLE();HAL_NVIC_SetPriority(DMA2_Stream0_IRQn, 0, 0);HAL_NVIC_EnableIRQ(DMA2_Stream0_IRQn);
}void HAL_ADC_MspInit(ADC_HandleTypeDef *adcHandle)
{GPIO_InitTypeDef GPIO_InitStruct = {0};if (adcHandle->Instance == ADC1){/* ADC1 clock enable */__HAL_RCC_ADC1_CLK_ENABLE();__HAL_RCC_GPIOB_CLK_ENABLE();/**ADC1 GPIO ConfigurationPB0 ------> ADC1_IN8PB1 ------> ADC1_IN9*/GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1;GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;GPIO_InitStruct.Pull = GPIO_NOPULL;HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);/* ADC1 DMA Init *//* ADC1 Init */hdma_adc1.Instance = DMA2_Stream0;hdma_adc1.Init.Channel = DMA_CHANNEL_0;hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY;hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE;hdma_adc1.Init.MemInc = DMA_MINC_ENABLE;hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;hdma_adc1.Init.Mode = DMA_CIRCULAR;hdma_adc1.Init.Priority = DMA_PRIORITY_HIGH;hdma_adc1.Init.FIFOMode = DMA_FIFOMODE_DISABLE;if (HAL_DMA_Init(&hdma_adc1) != HAL_OK){Error_Handler();}__HAL_LINKDMA(adcHandle, DMA_Handle, hdma_adc1);/* ADC1 interrupt Init */HAL_NVIC_SetPriority(ADC_IRQn, 0, 0);HAL_NVIC_EnableIRQ(ADC_IRQn);}
}void HAL_ADC_MspDeInit(ADC_HandleTypeDef *adcHandle)
{if (adcHandle->Instance == ADC1){__HAL_RCC_ADC1_CLK_DISABLE();/**ADC1 GPIO ConfigurationPB0 ------> ADC1_IN8PB1 ------> ADC1_IN9*/HAL_GPIO_DeInit(GPIOB, GPIO_PIN_0 | GPIO_PIN_1);/* ADC1 DMA DeInit */HAL_DMA_DeInit(adcHandle->DMA_Handle);/* ADC1 interrupt Deinit */HAL_NVIC_DisableIRQ(ADC_IRQn);}
}
测试环节
#define VREF 3.3f // 参考电压,理论上是3.3,可通过实际测量得3.258
#define ADC_NUM_MAX 2048 // ADC 转换结果缓冲区最大值
#define VBUS_MAX 15 // 电压最大值
#define VBUS_MIN 10 // 电压最小值uint16_t adc_buff[ADC_NUM_MAX];
uint16_t vbus_adc_mean = 0; // 电源电压 ACD 采样结果平均值
uint32_t adc_mean_sum = 0; // 平均值累加
uint32_t adc_mean_count = 0; // 累加计数
uint16_t flag_num = 0;/*** @brief 常规转换在非阻塞模式下完成回调* @param hadc: ADC 句柄.* @retval 无*/
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
{uint32_t adc_mean = 0;// 停止 ADC 采样,处理完一次数据在继续采样HAL_ADC_Stop_DMA(hadc); // 电流数据第一次滤波for (uint32_t count = 0; count < ADC_NUM_MAX; count += 2){adc_mean += (uint32_t)adc_buff[count];}adc_mean_sum += adc_mean / (ADC_NUM_MAX / 2); // 累加电压adc_mean_count++;adc_mean = 0;// 电压数据第一次滤波for (uint32_t count = 1; count < ADC_NUM_MAX; count += 2){adc_mean += (uint32_t)adc_buff[count];}vbus_adc_mean = adc_mean / (ADC_NUM_MAX / 2); // 保存平均值HAL_ADC_Start_DMA(hadc, (uint32_t *)&adc_buff, ADC_NUM_MAX); // 开始 ADC 采样
}/*** @brief 在非阻塞模式模拟看门狗回调* @param hadc: ADC 句柄.* @retval 无*/
void HAL_ADC_LevelOutOfWindowCallback(ADC_HandleTypeDef *hadc)
{float temp_adc;flag_num++; // 电源电压超过阈值电压temp_adc = get_vbus_val();if (temp_adc > VBUS_MIN && temp_adc < VBUS_MAX){flag_num = 0;}if (flag_num > 2048){set_motor_disable(); // 停止电机flag_num = 0;printf("电源电压超过限制!请检查原因,复位开发板在试!\r\n");while (1);}
}/*** @brief 获取电流值(应定时调用)* @param 无* @retval 转换得到的电流值*/
int32_t get_curr_val(void)
{static uint8_t flag = 0;static uint32_t adc_offset = 0; // 偏置电压int16_t curr_adc_mean = 0; // 电流 ACD 采样结果平均值// 电流数据第二次滤波curr_adc_mean = adc_mean_sum / adc_mean_count; // 保存平均值adc_mean_count = 0;adc_mean_sum = 0;if (flag < 17){adc_offset = curr_adc_mean; // 多次记录偏置电压,待系统稳定偏置电压才为有效值flag += 1;}if (curr_adc_mean >= adc_offset){curr_adc_mean -= adc_offset; // 减去偏置电压}else{curr_adc_mean = 0;}// 获取电压值float vdc = ((float)vbus_adc_mean / 4096.0f * VREF);// 得到电流值,电压放大8倍,0.02是采样电阻,单位mA。电流采样电路得知的return ( (float)vdc / 8.0f / 0.02f * 1000.0f )
}/*** @brief 获取电源电压值* @param 无* @retval 转换得到的电流值*/
float get_vbus_val(void)
{// 获取电压值float vdc = ((float)vbus_adc_mean / 4096.0f * VREF); // 电压最大值(测量电压是电源电压的1/37),电流、电压采样电路得知的return ( ((float)vdc - 1.24f) * 37.0f );
}#define CURR_MAX 500 // 最大电流(单位mA)void test(void)
{uint8_t curr_max_count = 0;uint8_t flag = 0;uint8_t dir = 0;初始化HAL_ADC_Start_DMA(&hadc1, (uint32_t *)&adc_buff, ADC_NUM_MAX);while(1){if (HAL_GetTick() % 50 == 0 && flag == 0) // 每50毫秒读取一次电流、电压{flag = 1;int32_t current = get_curr_val();printf("电源电压:%.2fV,电流:%dmA\r\n", get_vbus_val(), current);if (current > CURR_MAX) // 判断是不是超过限定的值{if (curr_max_count++ > 5) // 连续5次超过{set_motor_disable(); // 电机停止curr_max_count = 0;printf("电流超过限制!请检查原因,复位开发板在试!\r\n");while (1);}}}else if (HAL_GetTick() % 50 != 0 && flag == 1){flag = 0;} }
}