电机应用-直流有刷电机

目录

直流有刷电机

直流有刷电机简介 

直流有刷电机结构

直流有刷电机优缺点

直流有刷电机应用场景

直流有刷电机参数

直流有刷电机工作原理

直流有刷电机驱动板

直流有刷电机驱动板功能

直流有刷电机驱动板接口

直流有刷电机驱动板接线

直流有刷电机驱动板原理

H桥驱动原理

电流采集电路

电压采集电路

温度采集电路

PWM控制电机原理

基础驱动功能介绍

硬件资源介绍

课堂代码掌握

直流有刷电机实验

硬件资源和引脚分配

高级定时器TIM1初始化

直流电机操作

操作实验

电压、电流和温度检测

电压检测

电流检测

温度检测

ADC+DMA配置

电压电流温度采集

两个直流有刷减速电机按键控制

开发设计

定时器配置

测试环节

直流有刷减速电机驱动板电流电压采集

ADC配置

测试环节

直流有刷减速电机驱动板限电流、过电压、欠电压保护

ADC配置

测试环节


直流有刷电机

直流有刷电机简介 

直流有刷电机(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.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;
		}	
	}	
}

  • 4
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值