【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, ¶mMotorCmd1Length);
if (strncmp("stop", paramMotorCmd1, strlen("stop")) == 0) {
motor_stop();
sprintf(pcWriteBuffer, "\r\n motor_stop() \r\n");
} else {
/* 获取 speed */
paramMotorCmd2 = FreeRTOS_CLIGetParameter(pcCommandString, 2, ¶mMotorCmd2Length);
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]