大家好,欢迎来到IT知识分享网。
目录
直流有刷电机
直流有刷电机简介
直流有刷电机(BDC)是一种内含电刷装置的将直流电能转换为机械能的电动机。在允许的范围内,供电即可工作,改变电流方向即可反转,调整电压即可调整电机的转速,拥有良好的调速性能。
基本的直流有刷电机在电源和电机间只需要两根电缆,可以节省配线和连接器所需的空间,并降低电缆和连接器的成本。
直流有刷电机转速快、扭矩小,在某些应用中可能无法满足要求。直流有刷减速电机可以降低转速并提高力矩,也就能带动更大的负载。
直流有刷电机结构
直流有刷电机结构包含:定子、转子、电刷和换向器。
定子:N-S极,产生固定的磁场方向N→S。
转子:由一个或多个绕组构成,通电后在磁场中受力运动。
电刷:将外部电流输入到转子绕组上。
换向器:改变绕组中电流的流向。
直流有刷电机优缺点
优点 | 缺点 |
驱动简单、操控方便、成本低廉 | 寿命短、可靠性差、换向火花易产生电磁干扰 |
直流有刷电机应用场景
直流有刷电机常被应用于电动玩具、砂轮机、电风扇、低端电动自行车等方面。
功率 = 扭矩 * 角速度,角速度 = 2*Π*转速。
实际上在调速时有恒压调速和恒功率调速,恒压调速时功率会变。
一款电机的最大功率是固定的,根据物理推导,在功率一定的情况下,电机的转速和扭矩呈反比关系。所以对于扭矩有要求的场所,需要加上减速齿轮,以增大扭矩。
直流有刷电机参数
额定电压:电机正常工作的电压。一般电机铭牌上都会标明。
额定电流(负载电流):电机带负载正常工作的电流。
空载电流:电机不带负载正常工作的电流。
堵转电流:电机带负载过大而导致电机不能正常工作产生堵转的电流。
额定转速(负载转速):电机带负载正常工作的转速。单位是r/min(RPM)。
空载转速:电机不带负载正常工作的转速。单位是r/min(RPM)。
额定扭矩:电机额定电流下输出力的大小。单位常用kg·cm。
减速比(减速电机特有):电机原始转速和经过减速器后转速的比值,表示为N:1。
Tips:不用使用过大负载,防止堵转造成电机过热。
直流有刷电机工作原理
左手定则:大拇指和四指垂直,磁场方向垂直穿过掌心,四指指向电流方向,大拇指方向即为受力方向。
测速原理:编码器计数。
直流有刷电机驱动板
直流有刷电机驱动板功能
完整的H桥驱动
电流采集
电压、温度采集
编码器接口
直流有刷电机驱动板接口
直流有刷电机驱动板接线
驱动器丝印 | 电机标签 |
ENCA | 编码器A相(注意不能反接,否则短路甚至烧毁) |
ENCB | 编码器B相(注意不能反接,否则短路甚至烧毁) |
DGND | 编码器电源负极 |
VCC5 | 编码器电源正极 |
M+ | 电机M+ |
M- | 电机M- |
略。附链接05-直流有刷电机专题-第3讲 直流有刷驱动板入门(1)_哔哩哔哩_bilibili
直流有刷电机驱动板原理
H桥驱动原理
基本的驱动电路,用来控制电机的启停以及正反转的最基本电路。
下图为简易的H桥电路:
下图为改良后的电路:
电流采集电路
电压采集电路
温度采集电路
PWM控制电机原理
安培力公式:F = BIL = BLU/R。(B为磁感应强度,I是电流,L是导线垂直于磁感线的长度)
在R、B、L不变的情况下,控制安培力的大小,实际上是修改供电电压的大小。所以控制电机转速的本质是给电机供不同的供电电压。(电压越大,电机转速越快)
在直流有刷电机的控制中,我们常用PWM来控制电压的大小,以此改变直流有刷电机的转速。
在步进电机的控制中,步进电机接收的脉冲个数决定了它的旋转位置,脉冲频率决定了它的旋转速度。
基础驱动功能介绍
功能1:电机启停
实现方法:IO控制半桥芯片SD引脚的输入电平。
功能2:电机正反转
实现方法:利用两个IO进行翻转,但是需要注意死区时间(该半桥芯片自带死区控制的时间,为520ns)。
如:SD引脚为1、PWM_UH为1、PWM_UL为0。电流方向是VCC→M-→M+→GND。
如:SD引脚为1、PWM_UH为0、PWM_UL为1。电流方向是VCC→M+→M-→GND。
功能3:电机转速
实现方法:PWM_UH和PWM_UL,一个IO固定输出,另一个使用PWM输出。
如:SD引脚为1、PWM_UH为1、PWM_UL为1。M-和M+都流向GND,此时电机处于停止状态。
如:SD引脚为1、PWM_UH为1、PWM_UL为0。电流方向是VCC→M-→M+→GND。
功能4:电机正反转、调速
实现方法:PWM互补输出。
硬件资源介绍
电机开发板:TIM1_CH1(PA8)、TIM1_CH1N(PB13)、SD(PF10)、LED0(PE0)、KEY0~KEY2(PE2~PE4)。
直流有刷驱动板
直流有刷电机
12V DC电源
Tips:通电之前一定要检查电源和编码器接线。
课堂代码掌握
代码功能:KEY0增大PWM比较值变量,KEY1减小PWM比较值变量,比较值变量的大小决定了电机转速,正负号决定了电机的正反转,KEY2控制电机停止。
重要函数:
1、定时器初始化函数。
初始化通道IO,配置定时器、PWM互补输出以及死区控制等。
2、电机初始化函数
初始化SD引脚IO,默认先拉低SD引脚。
3、电机启动函数
开启定时器和拉高SD引脚。开启定时器函数:HAL_TIM_Base_Start
4、电机停止函数
关闭定时器和拉低SD引脚。开启定时器函数:HAL_TIM_Base_Stop
5、电机正反转函数
先关闭两个通道的PWM输出,正转就开启主通道,反转就开启互补通道。
关闭PWM输出函数:HAL_TIM_PWM_Stop、HAL_TIMEx_PWMN_Stop
直流有刷电机实验
硬件资源和引脚分配
LED0 – PE0
KEY0 – PE2
KEY1 – PE3
KEY2 – PE4
开发板有两个直流有刷电机接口,本实验用接口1,也就是CN13。
接口1(CN13)所用引脚:
TIM1CH1(PA8) – PWM主通道
TIM1CH1N(PB13) – PWM互补通道
PF10 – SD停止引脚(Shutdown)
接口2(CN16)所用引脚:
TIM8CH1(PI5) – PWM主通道
TIM8CH1N(PH13) – PWM互补通道
PF2 – SD停止引脚(Shutdown)
定时器底层驱动
定时器底层驱动,时钟使能,引脚配置。此函数会被HAL_TIM_PWM_Init()调用。而HAL_TIM_PWM_Init()函数会在定时器基础配置时调用。
/ * @brief 定时器底层驱动,时钟使能,引脚配置 此函数会被HAL_TIM_PWM_Init()调用 * @param htim:定时器句柄 * @retval 无 */ void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim) { if (htim->Instance == ATIM_TIMX_CPLM) { GPIO_InitTypeDef gpio_init_struct; ATIM_TIMX_CPLM_CHY_GPIO_CLK_ENABLE(); /* 通道X对应IO口时钟使能 */ ATIM_TIMX_CPLM_CHYN_GPIO_CLK_ENABLE(); /* 互补通道对应IO口时钟使能 */ ATIM_TIMX_CPLM_CLK_ENABLE(); /* 定时器x时钟使能 */ /* 配置PWM主通道引脚 */ gpio_init_struct.Pin = ATIM_TIMX_CPLM_CHY_GPIO_PIN; gpio_init_struct.Mode = GPIO_MODE_AF_PP; gpio_init_struct.Pull = GPIO_NOPULL; gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH ; gpio_init_struct.Alternate = ATIM_TIMX_CPLM_CHY_GPIO_AF; /* 端口复用 */ HAL_GPIO_Init(ATIM_TIMX_CPLM_CHY_GPIO_PORT, &gpio_init_struct); /* 配置PWM互补通道引脚 */ gpio_init_struct.Pin = ATIM_TIMX_CPLM_CHYN_GPIO_PIN; HAL_GPIO_Init(ATIM_TIMX_CPLM_CHYN_GPIO_PORT, &gpio_init_struct); } }
高级定时器TIM1初始化
高级定时器的时钟来自APB2,而PCLK2 = 168Mhz。
定时器基础配置参数介绍:
Prescaler:定时器时钟预分频系数,为实际分频-1。如PCLK2 = 168Mhz,Prescaler为0,则定时器时钟不分频,定时器时钟为168MHz。
Period:自动重装载值,为实际重装载值-1。如自动重装载值为16799,则定时器0.1ms进一次中断。
RepetitionCounter:重复计数器寄存器值。当经过0.1ms,RepetitionCounter不为零时自动减一,然后继续重装载计数;否则为零时,进入中断,然后继续重装载计数。
PWM1模式:
在递增计数模式下,只要 TIMx_CNT<TIMx_CCR1,通道 1 便为有效状态,否则为无效状态。
在递减计数模式下,只要 TIMx_CNT>TIMx_CCR1,通道 1 便为无效状态( OC1REF=“0”),否则为有效状态( OC1REF=“1”)
即PWM1模式无论是递增计数还是递减计数,TIMx_CNT<TIMx_CCR1时通道为有效状态。
PWM2模式:
在递增计数模式下,只要 TIMx_CNT<TIMx_CCR1,通道 1 便为无效状态,否则为有效状态。
在递减计数模式下,只要 TIMx_CNT>TIMx_CCR1,通道 1 便为有效状态,否则为无效状态。
即PWM2模式无论是递增计数还是递减计数,TIMx_CNT>TIMx_CCR1时通道为有效状态。
CH1N是CH1的反相,CH1N通道是会自动带着一个取反的。
测试现象
当PWM1递增模式,OCPolarity为低电平,OCNPolarity为低电平,8400的重装载值,比较值为1680。则主通道波形0.2低,0.8高。互补通道0.2高,0.8低。
当PWM1递增模式,OCPolarity为低电平,OCNPolarity为高电平,8400的重装载值,比较值为1680。则主通道和互补通道波形0.2低,0.8高。
当PWM1递增模式,OCPolarity为高电平,OCNPolarity为高电平,8400的重装载值,比较值为1680。则主通道波形0.2高,0.8低。互补通道0.2低,0.8高。
当PWM1递增模式,OCPolarity为高电平,OCNPolarity为低电平,8400的重装载值,比较值为1680。则主通道和互补通道波形0.2高,0.8低。
当PWM1递增模式,OCPolarity为低电平,OCNPolarity为低电平,8400的重装载值,比较值为1680。不开主通道,互补通道0.2低,0.8高。主通道高电平。
当PWM1递增模式,OCPolarity为低电平,OCNPolarity为低电平,8400的重装载值,比较值为1680。不开互补通道,主通道0.2低,0.8高。互补通道高电平。
当PWM1递增模式,OCPolarity为低电平,OCNPolarity为高电平,8400的重装载值,比较值为1680。不开主通道,互补通道0.2高,0.8低。主通道高电平。
当PWM1递增模式,OCPolarity为低电平,OCNPolarity为高电平,8400的重装载值,比较值为1680。不开互补通道,主通道0.2低,0.8高。互补通道低电平。
当PWM1递增模式,OCPolarity为高电平,OCNPolarity为高电平,8400的重装载值,比较值为1680。不开主通道,互补通道0.2高,0.8低。主通道低电平。
当PWM1递增模式,OCPolarity为高电平,OCNPolarity为高电平,8400的重装载值,比较值为1680。不开互补通道,主通道0.2高,0.8低。互补通道低电平。
当PWM1递增模式,OCPolarity为高电平,OCNPolarity为低电平,8400的重装载值,比较值为1680。不开主通道,互补通道0.2低,0.8高。主通道低电平。
当PWM1递增模式,OCPolarity为高电平,OCNPolarity为低电平,8400的重装载值,比较值为1680。不开互补通道,主通道0.2高,0.8低。互补通道低电平。
定时器PWM配置参数介绍:
OCMode:TIM_OCMODE_PWM1。在PWM1模式的递增计数模式下,只要TIMx_CNT<TIMx_CCR1,通道1便为有效状态,否则为无效状态。
Pulse:TIMx_CCR1比较值。
OCPolarity:主通道输出极性,即有效状态。
OCNPolarity:互补通道输出极性,即有效状态。
OCFastMode:比较输出快速模式。定时器能够更快地响应输出比较事件,从而产生更快的PWM信号。可用于要求较高的PWM输出频率和更快的响应时间的应用中。
OCIdleState:主通道的空闲状态。即关闭主通道PWM时的电平状态。
OCNIdleState:互补通道的空闲状态。即关闭主通道PWM时的电平状态。
在STM32定时器中,断路和死区是与PWM输出相关的功能。断路指的是在PWM信号周期内某些特定时间段内禁用输出,而死区则是为了避免电路中出现短路现象而设置的时间间隔。
Tips:OSSR和OSSI的介绍网上都是云里雾里,希望有个大佬简单抽象描述一下断路和死区配置参数。
断路和死区配置参数介绍:
OffStateRunMode:
OSSR为运行模式下的关闭状态选择,该位在MOE=1时作用于配置为输出模式且具有互补输出的通道。如果定时器中没有互补输出,则不存在OSSR。
0:当定时器不工作时,禁止OC_OCN输出(OC/OCN使能输出信号=0)
1:当定时器不工作时,一旦CCxE=1或CCxNE=1,便使能OC/OCN输出。将其设为无效电平,然后设置OC/OCN使能输出信号=1。
OffStateIDLEMode:
OSSI为空闲模式下的关闭状态选择,该位在MOE=0时作用于配置为输出的通道。
0:当定时器不工作时,禁止OC_OCN输出(OC/OCN使能输出信号=0)
1:当定时器不工作时,一旦CCxE=1或CCxNE=1,便将OC/OCN输出。首先强制为其空闲电平,然后设置OC/OCN使能输出信号=1。
LockLevel:锁定等级,分四个等级:0、1、2、3。对某些位提供写保护,既无法对某些位写入内容。该位上电只能写入一次。
DeadTime:死区时间。
BreakState:断路输入使能。
BreakPolarity:断路输入极性。
AutomaticOutput:定时器自动输出使能状态。
当PWM1递增模式,OCPolarity为低电平,OCNPolarity为低电平,8400的重装载值,比较值为1680。不开主通道,互补通道0.2低,0.8高。主通道高电平。
当PWM1递增模式,OCPolarity为低电平,OCNPolarity为低电平,8400的重装载值,比较值为1680。不开互补通道,主通道0.2低,0.8高。互补通道高电平。
Tips: 实验设置了两个通道在无效状态时的输出为高电平,即当某个通道关闭PWM输出时,该通道会一直输出高电平。在电机控制时,我们只需要开启其中一个通道的PWM输出,让另一个通道处于无效状态(一直处于高电平),这样即可控制PWM的输出以实现电机的调速。
/* 电机基本驱动 互补输出带死区控制程序 / TIM_HandleTypeDef g_atimx_cplm_pwm_handle; /* 定时器x句柄 */ / * @brief 高级定时器TIMX 互补输出 初始化函数(使用PWM模式1) * @note * 配置高级定时器TIMX 互补输出, 一路OCy 一路OCyN, 并且可以设置死区时间 * * 高级定时器的时钟来自APB2, 而PCLK2 = 168Mhz, 我们设置PPRE2不分频, 因此 * 高级定时器时钟 = 168Mhz * 定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us. * Ft=定时器工作频率, 单位 : Mhz * * @param arr: 自动重装值。 * @param psc: 时钟预分频数 * @retval 无 */ void atim_timx_cplm_pwm_init(uint16_t arr, uint16_t psc) { TIM_OC_InitTypeDef sConfigOC ; TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig; g_atimx_cplm_pwm_handle.Instance = ATIM_TIMX_CPLM; /* 定时器x */ g_atimx_cplm_pwm_handle.Init.Prescaler = psc; /* 定时器预分频系数 */ g_atimx_cplm_pwm_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 向上计数模式 */ g_atimx_cplm_pwm_handle.Init.Period = arr; /* 自动重装载值 */ g_atimx_cplm_pwm_handle.Init.RepetitionCounter = 0; /* 重复计数器寄存器为0 */ g_atimx_cplm_pwm_handle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE; /* 使能影子寄存器TIMx_ARR */ HAL_TIM_PWM_Init(&g_atimx_cplm_pwm_handle) ; /* 设置PWM输出 */ sConfigOC.OCMode = TIM_OCMODE_PWM1; /* PWM模式1 */ sConfigOC.Pulse = 0; /* 比较值为0 */ sConfigOC.OCPolarity = TIM_OCPOLARITY_LOW; /* OCy 低电平有效 */ sConfigOC.OCNPolarity = TIM_OCNPOLARITY_LOW; /* OCyN 低电平有效 */ sConfigOC.OCFastMode = TIM_OCFAST_ENABLE; /* 使用快速模式 */ sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET; /* 主通道的空闲状态 */ sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET; /* 互补通道的空闲状态 */ HAL_TIM_PWM_ConfigChannel(&g_atimx_cplm_pwm_handle, &sConfigOC, ATIM_TIMX_CPLM_CHY); /* 配置后默认清CCER的互补输出位 */ /* 设置死区参数,开启死区中断 */ sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_ENABLE; /* OSSR设置为1 */ sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE; /* OSSI设置为0 */ sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF; /* 上电只能写一次,需要更新死区时间时只能用此值 */ sBreakDeadTimeConfig.DeadTime = 0X0F; /* 死区时间 */ sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE; /* BKE = 0, 关闭BKIN检测 */ sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_LOW; /* BKP = 1, BKIN低电平有效 */ sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE; /* 使能AOE位,允许刹车后自动恢复输出 */ HAL_TIMEx_ConfigBreakDeadTime(&g_atimx_cplm_pwm_handle, &sBreakDeadTimeConfig); /* 设置BDTR寄存器 */ }
最终调用:atim_timx_cplm_pwm_init(8400 – 1, 0); /* 168 000 000 / 1 = 168 000 000 168Mhz的计数频率,计数8400次为50us */
直流电机操作
/* 基本驱动 */ /* 停止引脚操作宏定义 * 此引脚控制H桥是否生效以达到开启和关闭电机的效果 */ #define SHUTDOWN1_Pin GPIO_PIN_10 #define SHUTDOWN1_GPIO_Port GPIOF #define SHUTDOWN2_Pin GPIO_PIN_2 #define SHUTDOWN2_GPIO_Port GPIOF #define SHUTDOWN_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOF_CLK_ENABLE(); }while(0) /* PF口时钟使能 */ /* 电机停止引脚定义 这里默认是接口1 */ #define ENABLE_MOTOR HAL_GPIO_WritePin(SHUTDOWN1_GPIO_Port,SHUTDOWN1_Pin,GPIO_PIN_SET) #define DISABLE_MOTOR HAL_GPIO_WritePin(SHUTDOWN1_GPIO_Port,SHUTDOWN1_Pin,GPIO_PIN_RESET) // void dcmotor_init(void); /* 直流有刷电机初始化 */ void dcmotor_start(void); /* 开启电机 */ void dcmotor_stop(void); /* 关闭电机 */ void dcmotor_dir(uint8_t para); /* 设置电机方向 */ void dcmotor_speed(uint16_t para); /* 设置电机速度 */ void motor_pwm_set(float para); /* 电机控制 */
/* 基本驱动 */ extern TIM_HandleTypeDef g_atimx_cplm_pwm_handle; /* 定时器x句柄 */ / * @brief 电机初始化 * @param 无 * @retval 无 */ void dcmotor_init(void) { SHUTDOWN_GPIO_CLK_ENABLE(); GPIO_InitTypeDef gpio_init_struct; /* SD引脚设置,设置为推挽输出 */ gpio_init_struct.Pin = SHUTDOWN1_Pin|SHUTDOWN2_Pin; gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP; gpio_init_struct.Pull = GPIO_NOPULL; gpio_init_struct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(SHUTDOWN1_GPIO_Port, &gpio_init_struct); HAL_GPIO_WritePin(GPIOF, SHUTDOWN1_Pin|SHUTDOWN2_Pin, GPIO_PIN_RESET); /* SD拉低,关闭输出 */ dcmotor_stop(); /* 停止电机 */ dcmotor_dir(0); /* 设置正转 */ dcmotor_speed(0); /* 速度设置为0 */ dcmotor_start(); /* 开启电机 */ } / * @brief 电机开启 * @param 无 * @retval 无 */ void dcmotor_start(void) { ENABLE_MOTOR; /* 拉高SD引脚,开启电机 */ } / * @brief 电机停止 * @param 无 * @retval 无 */ void dcmotor_stop(void) { HAL_TIM_PWM_Stop(&g_atimx_cplm_pwm_handle, TIM_CHANNEL_1); /* 关闭主通道输出 */ HAL_TIMEx_PWMN_Stop(&g_atimx_cplm_pwm_handle, TIM_CHANNEL_1); /* 关闭互补通道输出 */ DISABLE_MOTOR; /* 拉低SD引脚,停止电机 */ } / * @brief 电机旋转方向设置 * @param para:方向 0正转,1反转 * @note 以电机正面,顺时针方向旋转为正转 * @retval 无 */ void dcmotor_dir(uint8_t para) { HAL_TIM_PWM_Stop(&g_atimx_cplm_pwm_handle, TIM_CHANNEL_1); /* 关闭主通道输出 */ HAL_TIMEx_PWMN_Stop(&g_atimx_cplm_pwm_handle, TIM_CHANNEL_1); /* 关闭互补通道输出 */ if (para == 0) /* 正转 */ { HAL_TIM_PWM_Start(&g_atimx_cplm_pwm_handle, TIM_CHANNEL_1); /* 开启主通道输出 */ } else if (para == 1) /* 反转 */ { HAL_TIMEx_PWMN_Start(&g_atimx_cplm_pwm_handle, TIM_CHANNEL_1); /* 开启互补通道输出 */ } } / * @brief 电机速度设置 * @param para:比较寄存器值 * @retval 无 */ void dcmotor_speed(uint16_t para) { if (para < (__HAL_TIM_GetAutoreload(&g_atimx_cplm_pwm_handle) - 0x0F)) /* 限速,非必须 */ { __HAL_TIM_SetCompare(&g_atimx_cplm_pwm_handle, TIM_CHANNEL_1, para); } } / * @brief 电机控制 * @param para: pwm比较值 ,正数电机为正转,负数为反转 * @note 根据传入的参数控制电机的转向和速度 * @retval 无 */ void motor_pwm_set(float para) { int val = (int)para; if (val >= 0) { dcmotor_dir(0); /* 正转 */ dcmotor_speed(val); } else { dcmotor_dir(1); /* 反转 */ dcmotor_speed(-val); } }
操作实验
while (1) { key = key_scan(0); /* 按键扫描 */ if(key == KEY0_PRES) /* 当key0按下 */ { motor_pwm += 400; /* 因为不同的电机最小启动电压不同,可能在第一次增加的时候电机还不能转起来 */ if (motor_pwm == 0) { dcmotor_stop(MOTOR_1); /* 停止则立刻响应 */ dcmotor_stop(MOTOR_2); motor_pwm = 0; } else { dcmotor_start(MOTOR_1); /* 开启电机 */ dcmotor_start(MOTOR_2); if (motor_pwm >= 8400) /* 限速 */ { motor_pwm = 8400; } } motor_pwm_set(motor_pwm,MOTOR_1); /* 设置电机方向、转速 */ motor_pwm_set(motor_pwm,MOTOR_2); } else if(key == KEY1_PRES) /* 当key1按下 */ { motor_pwm -= 400; if (motor_pwm == 0) { dcmotor_stop(MOTOR_1); /* 停止则立刻响应 */ dcmotor_stop(MOTOR_2); motor_pwm = 0; } else { dcmotor_start(MOTOR_1); /* 开启电机 */ dcmotor_start(MOTOR_2); if (motor_pwm <= -8400) /* 限速 */ { motor_pwm = -8400; } } motor_pwm_set(motor_pwm,MOTOR_1); /* 设置电机方向、转速 */ motor_pwm_set(motor_pwm,MOTOR_2); } else if(key == KEY2_PRES) /* 当key2按下 */ { LED1_TOGGLE(); dcmotor_stop(MOTOR_1); /* 关闭电机 */ dcmotor_stop(MOTOR_2); motor_pwm = 0; motor_pwm_set(motor_pwm,MOTOR_1); /* 设置电机方向、转速 */ motor_pwm_set(motor_pwm,MOTOR_2); } delay_ms(10); t++; if(t % 20 == 0) { LED0_TOGGLE(); /*LED0(红灯) 翻转*/ } }
电压、电流和温度检测
电压检测
代码实现:
1、ADC1的通道9(PB1)检测VBUS对应的ADC值,计算VBUS。VBUS = ADC值 * 3.3 / 4096。
2、根据VBUS计算POWER的电压,POWER = 25 * VBUS,即POWER = ADC值 * 3.3 / 4096 * 25。
电流检测
代码实现:
1、通过ADC1的通道8(PB0)检测电机未启动时Iout的电压,作为参考电压Vref。
2、检测电机启动后Iout的电压Vrun。
3、计算实际电流I。I =(Vrun – 参考电压Vref)/ 0.12A
温度检测
代码实现:
1、通过ADC1的通道0(PA0)检测VTEMP,算出Rt。Rt = 3.3 * 4700 / VTEMP – 4700。
2、根据Rt算出实际温度值。Rt = Rp * exp(B*1/T1 – 1/T2)。
Rt 是热敏电阻在T1温度下的阻值;
Rp是热敏电阻在T2常温下的标称阻值;
exp是e的n次方,e是自然常数,就是自然对数的底数,近似等于 2.;
B值是热敏电阻的重要参数,教程中用到的热敏电阻B值为3380;
这里T1和T2指的是开尔文温度,T2是常温25℃,即(273.15+25)K
T1就是所求的温度
ADC+DMA配置
ADC参数介绍:
ScanConvMode:扫描模式,存在多通道时使用。 此模式用于扫描一组模拟通道,ADC会扫描规则通道组或注入通道组中选择的所有通道。为组中的每个通道都执行一次转换。每次转换结束后,会自动转换该组中的下一个通道。
NbrOfConversion:转换的通道数。
EOCSelection:
选择ADC_EOC_SEQ_CONV时,表示ADC顺序转换的完成状态。当整个ADC的顺序转换(多个通道的转换)完成时,该标志位会被置位。
选择ADC_EOC_SINGLE_CONV时,表示ADC单次转换的完成状态。当单个通道的转换完成时,该标志位会被置位。
Tips:转换顺序为电压→温度→电流。
/* 电压、温度、电流 多通道ADC采集(DMA读取)*/ /* ADC及引脚 定义 */ #define ADC_ADCX_CH0_GPIO_PORT GPIOB #define ADC_ADCX_CH0_GPIO_PIN GPIO_PIN_1 #define ADC_ADCX_CH0_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0) /* PB口时钟使能 */ #define ADC_ADCX_CH1_GPIO_PORT GPIOA #define ADC_ADCX_CH1_GPIO_PIN GPIO_PIN_0 #define ADC_ADCX_CH1_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0) /* PA口时钟使能 */ #define ADC_ADCX_CH2_GPIO_PORT GPIOB #define ADC_ADCX_CH2_GPIO_PIN GPIO_PIN_0 #define ADC_ADCX_CH2_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0) /* PB口时钟使能 */ #define ADC_ADCX ADC1 #define ADC_ADCX_CH0 ADC_CHANNEL_9 /* 电压测量通道 */ #define ADC_ADCX_CH1 ADC_CHANNEL_0 /* 温度测量通道 */ #define ADC_ADCX_CH2 ADC_CHANNEL_8 /* 电流测量通道 */ #define ADC_ADCX_CHY_CLK_ENABLE() do{ __HAL_RCC_ADC1_CLK_ENABLE(); }while(0) /* ADC1 时钟使能 */ #define ADC_CH_NUM 3 /* 需要转换的通道数目 */ #define ADC_COLL 1000 /* 单采集次数 */ #define ADC_SUM ADC_CH_NUM * ADC_COLL /* 总采集次数 */ /* DMA传输相关 定义 */ #define ADC_ADCX_DMASx DMA2_Stream4 /* 数据流4 */ #define ADC_ADCX_DMASx_Chanel DMA_CHANNEL_0 /* 通道0 */ #define ADC_ADCX_DMASx_IRQn DMA2_Stream4_IRQn #define ADC_ADCX_DMASx_IRQHandler DMA2_Stream4_IRQHandler // void adc_init(void); /* ADC初始化 */ void adc_nch_dma_init(void); /* ADC DMA传输 初始化函数 */
/* 多通道ADC采集 DMA读取 */ ADC_HandleTypeDef g_adc_nch_dma_handle; /* 与DMA关联的ADC句柄 */ DMA_HandleTypeDef g_dma_nch_adc_handle; /* 与ADC关联的DMA句柄 */ uint16_t g_adc_value[ADC_CH_NUM * ADC_COLL] = {0}; /* 存储ADC原始值 */ uint16_t g_adc_val[ADC_CH_NUM]; /*ADC平均值存放数组*/ /* 电压、电流、温度 多通道ADC采集(DMA读取)程序*/ / * @brief ADC初始化函数 * @param 无 * @retval 无 */ void adc_init(void) { ADC_ChannelConfTypeDef sConfig = {0}; g_adc_nch_dma_handle.Instance = ADC_ADCX; /* ADCx */ g_adc_nch_dma_handle.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4; /* 4分频,ADCCLK = PCLK2/4 = 84/4 = 21Mhz */ g_adc_nch_dma_handle.Init.Resolution = ADC_RESOLUTION_12B; /* 12位模式 */ g_adc_nch_dma_handle.Init.ScanConvMode = ENABLE; /* 扫描模式 多通道使用 */ g_adc_nch_dma_handle.Init.ContinuousConvMode = ENABLE; /* 连续转换模式,转换完成之后接着继续转换 */ g_adc_nch_dma_handle.Init.DiscontinuousConvMode = DISABLE; /* 禁止不连续采样模式 */ g_adc_nch_dma_handle.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE; /* 使用软件触发 */ g_adc_nch_dma_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START; /* 软件触发 */ g_adc_nch_dma_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT; /* 右对齐 */ g_adc_nch_dma_handle.Init.NbrOfConversion = ADC_CH_NUM; /* 使用转换通道数,需根据实际转换通道去设置 */ g_adc_nch_dma_handle.Init.DMAContinuousRequests = ENABLE; /* 开启DMA连续转换请求 */ g_adc_nch_dma_handle.Init.EOCSelection = ADC_EOC_SEQ_CONV; HAL_ADC_Init(&g_adc_nch_dma_handle); /* 配置使用的ADC通道,采样序列里的第几个转换,增加或者减少通道需要修改这部分 */ sConfig.Channel = ADC_ADCX_CH0; sConfig.Rank = 1; sConfig.SamplingTime = ADC_SAMPLETIME_480CYCLES; HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &sConfig); sConfig.Channel = ADC_ADCX_CH1; sConfig.Rank = 2; HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &sConfig); sConfig.Channel = ADC_ADCX_CH2; sConfig.Rank = 3; HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &sConfig); } / * @brief ADC DMA传输 初始化函数 * @note 本函数还是使用adc_init对ADC进行大部分配置,有差异的地方再单独配置 * @param par : 外设地址 * @param mar : 存储器地址 * @retval 无 */ void adc_nch_dma_init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; ADC_ADCX_CHY_CLK_ENABLE(); /* 开启ADCx时钟 */ ADC_ADCX_CH0_GPIO_CLK_ENABLE(); /* 开启GPIO时钟 */ ADC_ADCX_CH1_GPIO_CLK_ENABLE(); ADC_ADCX_CH2_GPIO_CLK_ENABLE(); /* AD采集引脚模式设置,模拟输入 */ GPIO_InitStruct.Pin = ADC_ADCX_CH0_GPIO_PIN; GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(ADC_ADCX_CH0_GPIO_PORT, &GPIO_InitStruct); GPIO_InitStruct.Pin = ADC_ADCX_CH1_GPIO_PIN; HAL_GPIO_Init(ADC_ADCX_CH1_GPIO_PORT, &GPIO_InitStruct); GPIO_InitStruct.Pin = ADC_ADCX_CH2_GPIO_PIN; HAL_GPIO_Init(ADC_ADCX_CH2_GPIO_PORT, &GPIO_InitStruct); adc_init(); /* 初始化ADC */ if ((uint32_t)ADC_ADCX_DMASx > (uint32_t)DMA2) /* 大于DMA1_Channel7, 则为DMA2的通道了 */ { __HAL_RCC_DMA2_CLK_ENABLE(); /* DMA2时钟使能 */ } else { __HAL_RCC_DMA1_CLK_ENABLE(); /* DMA1时钟使能 */ } /* DMA配置 */ g_dma_nch_adc_handle.Instance = ADC_ADCX_DMASx; /* 数据流x */ g_dma_nch_adc_handle.Init.Channel = ADC_ADCX_DMASx_Chanel; /* DMA通道x */ g_dma_nch_adc_handle.Init.Direction = DMA_PERIPH_TO_MEMORY; /* DIR = 1 ,外设到存储器模式 */ g_dma_nch_adc_handle.Init.PeriphInc = DMA_PINC_DISABLE; /* 外设非增量模式 */ g_dma_nch_adc_handle.Init.MemInc = DMA_MINC_ENABLE; /* 存储器增量模式 */ g_dma_nch_adc_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; /* 外设数据长度:16位 */ g_dma_nch_adc_handle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; /* 存储器数据长度:16位 */ g_dma_nch_adc_handle.Init.Mode = DMA_CIRCULAR; /* 外设流控模式 */ g_dma_nch_adc_handle.Init.Priority = DMA_PRIORITY_MEDIUM; /* 中等优先级 */ HAL_DMA_Init(&g_dma_nch_adc_handle); __HAL_LINKDMA(&g_adc_nch_dma_handle,DMA_Handle,g_dma_nch_adc_handle); /* 把ADC和DMA关联,用DMA传输ADC数据 */ /* ADC DMA中断配置 */ HAL_NVIC_SetPriority(ADC_ADCX_DMASx_IRQn, 2, 1); HAL_NVIC_EnableIRQ(ADC_ADCX_DMASx_IRQn); HAL_ADC_Start_DMA(&g_adc_nch_dma_handle,(uint32_t *)g_adc_value,ADC_SUM); /* 开启ADC的DMA传输 */ } / * @brief DMA2 数据流4中断服务函数 * @param 无 * @retval 无 */ void ADC_ADCX_DMASx_IRQHandler(void) { HAL_DMA_IRQHandler(&g_dma_nch_adc_handle); } / * @brief ADC 采集中断服务回调函数 * @param 无 * @retval 无 */ void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) { if (hadc->Instance == ADC_ADCX) { HAL_ADC_Stop_DMA(&g_adc_nch_dma_handle); /* 关闭DMA转换 */ calc_adc_val(g_adc_val); /* 计算ADC的平均值 */ HAL_ADC_Start_DMA(&g_adc_nch_dma_handle, (uint32_t *)&g_adc_value, (uint32_t)(ADC_SUM)); /* 启动DMA转换 */ } }
电压电流温度采集
/* 第二部分 电压电流温度采集 / /* 电流计算公式: * I=(最终输出电压-初始参考电压)/(6*0.02)A * ADC值转换为电压值:电压=ADC值*3.3/4096,这里电压单位为V,我们换算成mV,4096/1000=4.096,后面就直接算出为mA * 整合公式可以得出电流 I= (当前ADC值-初始参考ADC值)* (3.3 / 4.096 / 0.12) */ #define ADC2CURT (float)(3.3f / 4.096f / 0.12f) /* 电压计算公式: * V_POWER = V_BUS * 25 * ADC值转换为电压值:电压=ADC值*3.3/4096 * 整合公式可以得出电压V_POWER= ADC值 *(3.3f * 25 / 4096) */ #define ADC2VBUS (float)(3.3f * 25 / 4096) float get_temp(uint16_t para); /* 获取温度值 */ void calc_adc_val(uint16_t * p); /* 计算ADC平均值 */ /*/
/* 第二部分 电压电流温度采集 / /* Rt = Rp *exp(B*(1/T1-1/T2)) Rt 是热敏电阻在T1温度下的阻值; Rp是热敏电阻在T2常温下的标称阻值; exp是e的n次方,e是自然常数,就是自然对数的底数,近似等于 2.; B值是热敏电阻的重要参数,教程中用到的热敏电阻B值为3380; 这里T1和T2指的是开尔文温度,T2是常温25℃,即(273.15+25)K T1就是所求的温度 */ const float Rp = 10000.0f; /* 10K */ const float T2 = (273.15f + 25.0f); /* T2 */ const float Bx = 3380.0f; /* B */ const float Ka = 273.15f; / * @brief 计算温度值 * @param para: 温度采集对应ADC通道的值(已滤波) * @note 计算温度分为两步: 1.根据ADC采集到的值计算当前对应的Rt 2.根据Rt计算对应的温度值 * @retval 温度值 */ float get_temp(uint16_t para) { float Rt; float temp; /* 第一步: Rt = 3.3 * 4700 / VTEMP - 4700 ,其中VTEMP就是温度检测通道采集回来的电压值,VTEMP = ADC值* 3.3/4096 由此我们可以计算出当前Rt的值:Rt = 3.3f * 4700.0f / (para * 3.3f / 4096.0f ) - 4700.0f; */ Rt = 3.3f * 4700.0f / (para * 3.3f / 4096.0f ) - 4700.0f; /* 根据当前ADC值计算出Rt的值 */ /* 第二步: 根据当前Rt的值来计算对应温度值:Rt = Rp *exp(B*(1/T1-1/T2)) */ temp = Rt / Rp; /* 解出exp(B*(1/T1-1/T2)) ,即temp = exp(B*(1/T1-1/T2)) */ temp = log(temp); /* 解出B*(1/T1-1/T2) ,即temp = B*(1/T1-1/T2) */ temp /= Bx; /* 解出1/T1-1/T2 ,即temp = 1/T1-1/T2 */ temp += (1.0f / T2); /* 解出1/T1 ,即temp = 1/T1 */ temp = 1.0f / (temp); /* 解出T1 ,即temp = T1 */ temp -= Ka; /* 计算T1对应的摄氏度 */ return temp; /* 返回温度值 */ } extern uint16_t g_adc_value[ADC_CH_NUM * ADC_COLL]; / * @brief 计算ADC的平均值(滤波) * @param * p :存放ADC值的指针地址 * @note 此函数对电压、温度、电流对应的ADC值进行滤波, * p[0]-p[2]对应的分别是电压、温度和电流 * @retval 无 */ void calc_adc_val(uint16_t * p) { uint32_t temp[3] = {0,0,0}; int i; for(i=0;i<ADC_COLL;i++) /* 循环ADC_COLL次取值,累加 */ { temp[0] += g_adc_value[0+i*ADC_CH_NUM]; temp[1] += g_adc_value[1+i*ADC_CH_NUM]; temp[2] += g_adc_value[2+i*ADC_CH_NUM]; } temp[0] /= ADC_COLL; /* 取平均值 */ temp[1] /= ADC_COLL; temp[2] /= ADC_COLL; p[0] = temp[0]; /* 存入电压ADC通道平均值 */ p[1] = temp[1]; /* 存入温度ADC通道平均值 */ p[2] = temp[2]; /* 存入电流ADC通道平均值 */ }
extern uint16_t g_adc_val[ADC_CH_NUM]; /*ADC平均值存放数组*/ int main(void) { uint16_t init_adc_val; /* ...初始化工作,启动ADC转换 */ /* init_adc_val存储电流测量对应的参考电压ADC值,这里进行滤波 */ init_adc_val = g_adc_val[2]; /* 取出第一次得到的值 */ for(t=0;t<1000;t++) { init_adc_val += g_adc_val[2]; /* 现在的值和上一次存储的值相加 */ init_adc_val /= 2; /* 取平均值 */ delay_ms(1); } /* 参考电压要在电机未启动时测量 */ while (1) { /* ... */ delay_ms(10); t++; if(t % 20 == 0) { LED0_TOGGLE(); /*LED0(红灯) 翻转*/ printf("Valtage:%.1fV \r\n", g_adc_val[0]*ADC2VBUS); /* 打印电压值*/ printf("Temp:%.1fC \r\n", get_temp(g_adc_val[1])); /* 打印温度值*/ printf("Current:%.1fmA \r\n", abs(g_adc_val[2]-init_adc_val)*ADC2CURT); /* 打印电流值*/ printf("\r\n"); } } }
两个直流有刷减速电机按键控制
以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); } } }
直流有刷减速电机驱动板电流电压采集
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 Configuration PB0 ------> ADC1_IN8 PB1 ------> 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 Configuration PB0 ------> ADC1_IN8 PB1 ------> 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.258 static 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 Configuration PB0 ------> ADC1_IN8 PB1 ------> 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 Configuration PB0 ------> ADC1_IN8 PB1 ------> 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; } } }
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/147111.html