目录
直流有刷电机
直流有刷电机简介
直流有刷电机(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- |
直流有刷电机驱动板原理
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.7182818;
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.7182818;
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;
}
}
}