eefocus_3891719 发表于 2024-11-14 20:02:43

【Avnet | NXP FRDM-MCXN947试用活动】MCXN947 PWM 驱动直流电机驱...

# 背景

## 直流电机驱动模块 L9110S

一个 L9110S 驱动可以控制一个电机,下图中的 GroundStudio L9110s 模块板载两个 L9110s 芯片,可以驱动两个直流电机。

!(https://www.eefocus.com/forum/data/attachment/forum/202411/14/195326yb4wvas4sw5x0w09.png)

## 引脚说明

此模块有6根引脚,如下:

| 编号| 符号    | 说明         |
| --- | ----- | ------------ |
| 1   | VCC   | 供电, 3~5V, DC |
| 2   | GND   | 接地,电源负极      |
| 3   | M1(A) | Motor1-A输入端|
| 4   | M1(B) | Motor1-B输入端|
| 5   | M2(A) | Motor2-A输入端|
| 6   | M2(B) | Motor2-B输入端|

简单来说 L9110S 的输入输出有以下四种情形:

| 编号| IN1 | IN2 | OUT1 | OUT2 | 说明    |
| --- | --- | --- | ---- | ---- | ----- |
| 1   | 高电平 | 低电平 | 高电平| 低电平| 正转    |
| 2   | 低电平 | 高电平 | 低电平| 高电平| 反转    |
| 3   | 高电平 | 高电平 | 低电平| 低电平| 刹车,停转 |
| 4   | 低电平 | 低电平 | 高阻态| 高阻态| 滑行,停转 |

从上图可知,只需要给 **IN1** 和 **IN2** 管脚输入不同的电平就能实现正转、反转,输入相同的电平就能停转。

需要注意,不要直接由开发板来给模块供电,因为 L9110S 模块可能因为需求的驱动功率太高而导致板子上的供电不平衡

## MCXN947 PWM

从上表可知,使用GPIO给 **IN1** 和 **IN2** 也能驱动电机,但是不能控制速度。现在我们需要实现能控制速度的电机驱动。自然而然想到了 PWM 调速。

假设给 **IN2** 管脚用 GPIO 控制,拉低电平,只需要 PWM 往 **IN1** 灌高电平就可以驱动电机正转,调节 PWM 占空比就可以实现电机转速;把 **IN2** 管脚拉高电平,用 PMW 往 **IN1** 灌低电平就可以实现电机反转,注意此时的占空比和转速是反着的,即占空比越大,转速越慢。

这里选择 eFlexPWM 产生 PWM1_A 通道的 PWM 波来控制电机,另外一个管脚用 GPIO 来控制电平调节正反转。

# 代码实现

为了方便调试,先实现命令行接口,可以方便的通过串口输入命令调节参数控制电机正反转和转速。然后实现对应的电机驱动接口。

## 命令行接口

FreeRTOS-CLI 新增命令行,支持如下的3种命令:

```shell
motorstop       // 电机停转
motorleft   speed // 电机正转,speed 表示速度,取值范围
motorrightspeed // 电机反转,speed 表示范围,取值范围
```

当前命令行的解析函数 `prvMotorCommand()` 如下,其实最终调用的函数是分别是 `motor_stop()` , `motor_left(speed)` 和 `motor_right(speed)` 这三个函数。

```c
/**
* @brief 直流电机命令的实现
*
* motor left/right speed 其中 speed 取值范围是
* motor stop
*
* 示例:
* motor left 0
* motor left 100
* motor right 20
* motor right 80
* motor stop 停转
*
* @param pcWriteBuffer
* @param xWriteBufferLen
* @param pcCommandString
* @return BaseType_t
*/
static BaseType_t prvMotorCommand( char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString )
{
        configASSERT(pcWriteBuffer);

        /* param1: left/right/stop */
        const char *paramMotorCmd1 = NULL;
        BaseType_t paramMotorCmd1Length = 0;

        /* param2: speed */
        const char *paramMotorCmd2 = NULL;
        BaseType_t paramMotorCmd2Length = 0;
        uint32_t speed = 0;

        // 首先清除输出缓冲区旧的内容
        memset(pcWriteBuffer, 0, xWriteBufferLen);

        /*          arg0arg1       arg2   */
        /* 命令形式1:motor left/right speed*/
        /* 命令形式2:motor stop            */
        paramMotorCmd1 = FreeRTOS_CLIGetParameter(pcCommandString, 1, &paramMotorCmd1Length);

        if (strncmp("stop", paramMotorCmd1, strlen("stop")) == 0) {
                motor_stop();
                sprintf(pcWriteBuffer, "\r\n motor_stop() \r\n");
        } else {
                /* 获取 speed */
                paramMotorCmd2 = FreeRTOS_CLIGetParameter(pcCommandString, 2, &paramMotorCmd2Length);
                if (paramMotorCmd2 != NULL) {
                        speed = strtoul(paramMotorCmd2, NULL, 10);

                        if (speed >= 100) {
                                speed = 99; /* NOTE: PWM 占空比 <= 100 但由于两个高电平导致电机停转,所以占空比应该 < 100 */
                        }
                }

                if (strncmp("left", paramMotorCmd1, strlen("left")) == 0) {
                        sprintf(pcWriteBuffer, "\r\n motor_left(%u) \r\n", speed);
                        motor_left(speed);
                } else if (strncmp("right", paramMotorCmd1, strlen("right")) == 0) {
                        sprintf(pcWriteBuffer, "\r\n motor_right(%u) \r\n", speed);
                        motor_right(speed);
                } else {
                        sprintf(pcWriteBuffer, "\r\nError: arg1 should be left/right\r\n");
                }
        }

        /* There is no more data to return after this single string, so return pdFALSE. */
        return pdFALSE;
}
```

## 电机驱动实现

### PWM 和 GPIO 初始化

通过 MCUXpresso Config Tools 来实现,配置 **J3.15** 为 **PWM1_A0** ,配置 **J3.13** 为 GPIO 输出模式。生成代码,自动保存在 `pin_mux.c` 文件中。

!(https://www.eefocus.com/forum/data/attachment/forum/202411/14/195356e3ze84n4jtttjigq.png)

- 在 MCUXpresso Config Tools 中,点击(1)处新建分组 **BOARD_MOTOR_Init**,把电机相关的管脚都放在同一个组里初始化;
- 在(2)处可以看到 **J3.15** 配置为 **PWM1_A0** 信号,**J3.13** 配置为 **PIO2_7** 且为输出;
- 在(3)处给管脚添加标识符,会生成相对应的宏定义;

对应的管脚初始化代码如下:

```c
void BOARD_MOTOR_Init(void)
{
    /* Enables the clock for GPIO2: Enables clock */
    CLOCK_EnableClock(kCLOCK_Gpio2);
    /* Enables the clock for PORT2: Enables clock */
    CLOCK_EnableClock(kCLOCK_Port2);

    gpio_pin_config_t MOTOR_DIR_config = {
      .pinDirection = kGPIO_DigitalOutput,
      .outputLogic = 0U
    };
    /* Initialize GPIO functionality on pin PIO2_7 (pin L2)*/
    GPIO_PinInit(MOTOR_MOTOR_DIR_GPIO, MOTOR_MOTOR_DIR_PIN, &MOTOR_DIR_config);

    /* PORT2_6 (pin K2) is configured as PWM1_A0 */
    PORT_SetPinMux(MOTOR_MOTOR_SPEED_PORT, MOTOR_MOTOR_SPEED_PIN, kPORT_MuxAlt5);

    PORT2->PCR = ((PORT2->PCR &
                      /* Mask bits to zero which are setting */
                      (~(PORT_PCR_IBE_MASK)))

                     /* Input Buffer Enable: Enables. */
                     | PORT_PCR_IBE(PCR_IBE_ibe1));

    /* PORT2_7 (pin L2) is configured as PIO2_7 */
    PORT_SetPinMux(MOTOR_MOTOR_DIR_PORT, MOTOR_MOTOR_DIR_PIN, kPORT_MuxAlt0);

    PORT2->PCR = ((PORT2->PCR &
                      /* Mask bits to zero which are setting */
                      (~(PORT_PCR_IBE_MASK)))

                     /* Input Buffer Enable: Enables. */
                     | PORT_PCR_IBE(PCR_IBE_ibe1));
}
```

### 电机控制

需要实现4个函数,分别是:

- `motor_init()`
- `motor_stop()`
- `motor_left()`
- `motor_right()`

#### 公共的类型和变量

```c
#include "pin_mux.h"
#include "fsl_gpio.h"
#include "fsl_pwm.h"
#include "bsp_motor.h"
#include "fsl_debug_console.h"

/*******************************************************************************
* Definitions
******************************************************************************/
/* The PWM base address */
#define BOARD_PWM_BASEADDR PWM1
#define PWM_SRC_CLK_FREQ CLOCK_GetFreq(kCLOCK_BusClk)
#define DEMO_PWM_FAULT_LEVEL true
#define APP_DEFAULT_PWM_FREQUENCY (10000UL)
/* Definition for default PWM frequence in hz. */
#ifndef APP_DEFAULT_PWM_FREQUENCY
#define APP_DEFAULT_PWM_FREQUENCY (1000UL)
#endif

/* Macros ----------------------------------------------------------------------------------------*/
#define MOTOR_DIR_SET_LEFT()      GPIO_PinWrite(MOTOR_MOTOR_DIR_GPIO, MOTOR_MOTOR_DIR_GPIO_PIN, 0)
#define MOTOR_DIR_SET_RIGHT()   GPIO_PinWrite(MOTOR_MOTOR_DIR_GPIO, MOTOR_MOTOR_DIR_GPIO_PIN, 1)


/* Data Types ------------------------------------------------------------------------------------*/

typedef enum {
        MOTOR_DIR_LEFT = 0,
        MOTOR_DIR_RIGHT = 1,
} motor_dir_e;

static uint32_t pwmSourceClockInHz;
static uint32_t pwmFrequencyInHz;
static pwm_signal_param_t pwmSignal = { 0 };
static motor_dir_e m_motor_dir = MOTOR_DIR_LEFT;
```

#### motor_init()

```c
static void PWM_DRV_Init2PhPwm(void)
{
        uint16_t deadTimeVal;

        /* Set deadtime count, we set this to about 650ns */
        deadTimeVal = ((uint64_t)pwmSourceClockInHz * 650) / 1000000000;

        pwmSignal.pwmChannel = kPWM_PwmA;
        pwmSignal.level = kPWM_HighTrue;
        pwmSignal.dutyCyclePercent = 30; /* x percent dutycycle */
        pwmSignal.deadtimeValue = deadTimeVal;
        pwmSignal.faultState = kPWM_PwmFaultState0;
        pwmSignal.pwmchannelenable = true;

        /*********** PWMA_SM0 - phase A, configuration, setup 2 channel as an example ************/
        PWM_SetupPwm(BOARD_PWM_BASEADDR, kPWM_Module_0, &pwmSignal, 1,
                kPWM_SignedCenterAligned, pwmFrequencyInHz, pwmSourceClockInHz);
}

/**
* @brief 直流电机初始化,即 PWM1 的通道A和通道B初始化
*
* @return int 0 on success, others on failure.
*/
int motor_init(void)
{
        pwm_config_t pwmConfig;
        pwm_fault_param_t faultConfig;

        pwmSourceClockInHz = PWM_SRC_CLK_FREQ;
        pwmFrequencyInHz = APP_DEFAULT_PWM_FREQUENCY;

        SYSCON->PWM1SUBCTL |= SYSCON_PWM1SUBCTL_CLK0_EN_MASK; /* 只使能子模块0 */

        PWM_GetDefaultConfig(&pwmConfig);

        pwmConfig.reloadLogic = kPWM_ReloadPwmFullCycle;/* use full cycle reload */
        pwmConfig.pairOperation = kPWM_Independent;       /* PWM A & PWM B operate as 2 independent channels */
        pwmConfig.enableDebugMode = true;

        if (PWM_Init(BOARD_PWM_BASEADDR, kPWM_Module_0, &pwmConfig) == kStatus_Fail)
        {
                PRINTF("PWM initialization failed\r\n");
                return 1;
        }

        PWM_FaultDefaultConfig(&faultConfig);

#ifdef DEMO_PWM_FAULT_LEVEL
        faultConfig.faultLevel = DEMO_PWM_FAULT_LEVEL;
#endif

        /* Sets up the PWM fault protection */
        PWM_SetupFaults(BOARD_PWM_BASEADDR, kPWM_Fault_0, &faultConfig);
        PWM_SetupFaults(BOARD_PWM_BASEADDR, kPWM_Fault_1, &faultConfig);
        PWM_SetupFaults(BOARD_PWM_BASEADDR, kPWM_Fault_2, &faultConfig);
        PWM_SetupFaults(BOARD_PWM_BASEADDR, kPWM_Fault_3, &faultConfig);

        /* Set PWM fault disable mapping for submodule 0 */
        PWM_SetupFaultDisableMap(BOARD_PWM_BASEADDR, kPWM_Module_0, kPWM_PwmA, kPWM_faultchannel_0,
                                                       kPWM_FaultDisable_0 | kPWM_FaultDisable_1 | kPWM_FaultDisable_2 | kPWM_FaultDisable_3);

        /* Call the init function with demo configuration */
        PWM_DRV_Init2PhPwm();

        /* Set the load okay bit for all submodules to load registers from their buffer */
        PWM_SetPwmLdok(BOARD_PWM_BASEADDR, kPWM_Control_Module_0, true);

        PWM_StartTimer(BOARD_PWM_BASEADDR, kPWM_Control_Module_0);

        motor_stop();

        return 0;
}
```

#### motor_stop()

```c
void motor_stop(void)
{
        if (m_motor_dir == MOTOR_DIR_LEFT) {
                MOTOR_DIR_SET_LEFT();
                PWM_UpdatePwmDutycycle(BOARD_PWM_BASEADDR, kPWM_Module_0, kPWM_PwmA, kPWM_SignedCenterAligned, 0);
                /* Set the load okay bit for all submodules to load registers from their buffer */
                PWM_SetPwmLdok(BOARD_PWM_BASEADDR, kPWM_Control_Module_0, true);
        } else {
                MOTOR_DIR_SET_RIGHT();
                PWM_UpdatePwmDutycycle(BOARD_PWM_BASEADDR, kPWM_Module_0, kPWM_PwmA, kPWM_SignedCenterAligned, 100);
                /* Set the load okay bit for all submodules to load registers from their buffer */
                PWM_SetPwmLdok(BOARD_PWM_BASEADDR, kPWM_Control_Module_0, true);
        }
}
```

#### motor_left(speed)

```c
/**
* @brief 正转
* @param speed 占空比,
*/
void motor_left(uint32_t speed)
{
        MOTOR_DIR_SET_LEFT();
        m_motor_dir = MOTOR_DIR_LEFT;

        PWM_UpdatePwmDutycycle(BOARD_PWM_BASEADDR, kPWM_Module_0, kPWM_PwmA, kPWM_SignedCenterAligned, speed);
        /* Set the load okay bit for all submodules to load registers from their buffer */
        PWM_SetPwmLdok(BOARD_PWM_BASEADDR, kPWM_Control_Module_0, true);
        PWM_StartTimer(BOARD_PWM_BASEADDR, kPWM_Control_Module_0);
}
```

#### motor_right(speed)

```c
/**
* @brief 反转
* @param speed
*/
void motor_right(uint32_t speed)
{
        MOTOR_DIR_SET_RIGHT();
        m_motor_dir = MOTOR_DIR_RIGHT;
        speed = 100 - speed;

        PWM_UpdatePwmDutycycle(BOARD_PWM_BASEADDR, kPWM_Module_0, kPWM_PwmA, kPWM_SignedCenterAligned, speed);
        // PWM_SetChannelOutput(BOARD_PWM_BASEADDR, kPWM_Module_0, kPWM_PwmA, kPWM_InvertState);
        /* Set the load okay bit for all submodules to load registers from their buffer */
        PWM_SetPwmLdok(BOARD_PWM_BASEADDR, kPWM_Control_Module_0, true);

        PWM_StartTimer(BOARD_PWM_BASEADDR, kPWM_Control_Module_0);
}
```



# 验证

## 示波器测量

- 上面黄色的是 CH2,即 **PWM1_A** 信号(**J3.15**);
- 下面绿色的是 CH1,即 **GPIO** 信号 (**J3.13**);
- 电机正转 **motor left** 时 CH1 输出**低电平**;
- 电机反转 **motor right** 时 CH1 输出**高电平**;

### motor stop

两路信号都是低电平。电机停转。

!(https://www.eefocus.com/forum/data/attachment/forum/202411/14/195417asar87j5jzy3zbrm.png)

### motor left 5

电机正转,速度为5。示波器观测 PWM1_A 占空比为5.

!(https://www.eefocus.com/forum/data/attachment/forum/202411/14/195429j6avqatbqx6exqz6.png)

### motor left 70

电机正转,速度为70.示波器观测 PWM1_A 占空比为70.

!(https://www.eefocus.com/forum/data/attachment/forum/202411/14/195443yxcgigtww8tisp2d.png)

### motor left 100

虽然 PWM 占空比可以设置成100%,但是两个高电平导致电机停转,所以这里把占空比合理的范围是 。

!(https://www.eefocus.com/forum/data/attachment/forum/202411/14/195501vr8jwcki8rjcsox2.png)

### motor right 5

!(https://www.eefocus.com/forum/data/attachment/forum/202411/14/195513ui82oqlefyoyf8my.png)

### motor right 70

!(https://www.eefocus.com/forum/data/attachment/forum/202411/14/195525kjq85ze2fye5f95u.png)

### motor right 100

!(https://www.eefocus.com/forum/data/attachment/forum/202411/14/195537fikd0n7euuchuuzt.png)

## 电机实测

视频见B站: (https://www.bilibili.com/video/BV1qMULYYEx8/?vd_source=8f2bbf56b70c541bec2ea0b9f102ebee)

# 总结

页: [1]
查看完整版本: 【Avnet | NXP FRDM-MCXN947试用活动】MCXN947 PWM 驱动直流电机驱...