前言
恩智浦“FRDM-MCXN947”评测活动由安富利和与非网协同举办。本篇内容由与非网用户发布,已获转载许可。原文可在与非网(eefocus)工程师社区查看。
背景
01 直流电机驱动模块L9110S
一个L9110S驱动可以控制一个电机,下图中的GroundStudio L9110s模块板载两个L9110s芯片,可以驱动两个直流电机。
02 引脚说明
此模块有6根引脚,如下:
简单来说L9110S的输入输出有以下四种情形:
从上图可知,只需要给IN1和IN2管脚输入不同的电平就能实现正转、反转,输入相同的电平就能停转。
需要注意,不要直接由开发板来给模块供电,因为L9110S模块可能因为需求的驱动功率太高而导致板子上的供电不平衡。
03 MCXN947 PWM
从上表可知,使用GPIO给IN1和IN2也能驱动电机,但是不能控制速度。现在我们需要实现能控制速度的电机驱动。自然而然想到了PWM调速。
假设给IN2管脚用GPIO控制,拉低电平,只需要PWM往IN1灌高电平就可以驱动电机正转,调节PWM占空比就可以实现电机转速;把IN2管脚拉高电平,用PMW往IN1灌低电平就可以实现电机反转,注意此时的占空比和转速是反着的,即占空比越大,转速越慢。
这里选择eFlexPWM产生PWM1_A通道的PWM波来控制电机,另外一个管脚用GPIO来控制电平调节正反转。
04 代码实现
为了方便调试,先实现命令行接口,可以方便的通过串口输入命令调节参数控制电机正反转和转速。然后实现对应的电机驱动接口。
05 命令行接口
FreeRTOS-CLI新增命令行,支持如下的3种命令:
(滑动查看)
motor stop // 电机停转 motor left speed // 电机正转,speed 表示速度,取值范围[0, 100] motor right speed // 电机反转,speed 表示范围,取值范围[0, 100]
当前命令行的解析函数prvMotorCommand()如下,其实最终调用的函数是分别是motor_stop(),motor_left(speed)和motor_right(speed)这三个函数。
(滑动查看)
/** * @brief 直流电机命令的实现 * * motor left/right speed 其中 speed 取值范围是 [0, 100] * 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 */ staticBaseType_tprvMotorCommand(char*pcWriteBuffer,size_txWriteBufferLen,constchar*pcCommandString ) { configASSERT(pcWriteBuffer); br /* param1: left/right/stop */ constchar*paramMotorCmd1 =NULL; BaseType_t paramMotorCmd1Length =0; br /* param2: speed */ constchar*paramMotorCmd2 =NULL; BaseType_t paramMotorCmd2Length =0; uint32_tspeed =0; br // 首先清除输出缓冲区旧的内容 memset(pcWriteBuffer,0, xWriteBufferLen); br /* arg0 arg1 arg2 */ /* 命令形式1:motor left/right speed */ /* 命令形式2:motor stop */ paramMotorCmd1 =FreeRTOS_CLIGetParameter(pcCommandString,1, ¶mMotorCmd1Length); br if(strncmp("stop", paramMotorCmd1, strlen("stop")) ==0){ motor_stop(); sprintf(pcWriteBuffer," motor_stop() "); }else{ /* 获取 speed */ paramMotorCmd2 =FreeRTOS_CLIGetParameter(pcCommandString,2, ¶mMotorCmd2Length); if(paramMotorCmd2 !=NULL) { speed =strtoul(paramMotorCmd2,NULL,10); br if(speed >=100){ speed =99;/*NOTE:PWM 占空比 <= 100 但由于两个高电平导致电机停转,所以占空比应该 < 100 */ } } br if (strncmp("left", paramMotorCmd1, strlen("left")) == 0) { sprintf(pcWriteBuffer, " motor_left(%u) ", speed); motor_left(speed); } else if (strncmp("right", paramMotorCmd1, strlen("right")) == 0) { sprintf(pcWriteBuffer, " motor_right(%u) ", speed); motor_right(speed); } else { sprintf(pcWriteBuffer, " Error: arg1 should be left/right "); } } br /* There is no more data to return after this single string, so return pdFALSE. */ return pdFALSE; }
06 电机驱动实现
PWM和GPIO初始化
通过MCUXpresso Config Tools来实现,配置J3.15为PWM1_A0,配置J3.13为GPIO输出模式。生成代码,自动保存在pin_mux.c文件中。
在MCUXpresso Config Tools中,点击(1)处新建分组BOARD_MOTOR_Init,把电机相关的管脚都放在同一个组里初始化;
在(2)处可以看到J3.15配置为PWM1_A0信号,J3.13配置为PIO2_7且为输出;
在(3)处给管脚添加标识符,会生成相对应的宏定义。
对应的管脚初始化代码如下:
(滑动查看)
voidBOARD_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[6] = ((PORT2->PCR[6] & /* 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[7] = ((PORT2->PCR[7] & /* 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()
公共的类型和变量
(滑动查看)
#include"pin_mux.h" #include"fsl_gpio.h" #include"fsl_pwm.h" #include"bsp_motor.h" #include"fsl_debug_console.h" br /******************************************************************************* * Definitions ******************************************************************************/ /* The PWM base address */ #defineBOARD_PWM_BASEADDR PWM1 #definePWM_SRC_CLK_FREQ CLOCK_GetFreq(kCLOCK_BusClk) #defineDEMO_PWM_FAULT_LEVEL true #defineAPP_DEFAULT_PWM_FREQUENCY (10000UL) /* Definition for default PWM frequence in hz. */ #ifndefAPP_DEFAULT_PWM_FREQUENCY #defineAPP_DEFAULT_PWM_FREQUENCY (1000UL) #endif br /* Macros ------------*/ #defineMOTOR_DIR_SET_LEFT() GPIO_PinWrite(MOTOR_MOTOR_DIR_GPIO, MOTOR_MOTOR_DIR_GPIO_PIN, 0) #defineMOTOR_DIR_SET_RIGHT() GPIO_PinWrite(MOTOR_MOTOR_DIR_GPIO, MOTOR_MOTOR_DIR_GPIO_PIN, 1) br br /* Data Types --------*/ br typedefenum{ MOTOR_DIR_LEFT =0, MOTOR_DIR_RIGHT =1, } motor_dir_e; br staticuint32_tpwmSourceClockInHz; staticuint32_tpwmFrequencyInHz; staticpwm_signal_param_tpwmSignal = {0}; staticmotor_dir_e m_motor_dir = MOTOR_DIR_LEFT;
motor_init()
(滑动查看)
staticvoidPWM_DRV_Init2PhPwm(void) { uint16_tdeadTimeVal; br /* Set deadtime count, we set this to about 650ns */ deadTimeVal = ((uint64_t)pwmSourceClockInHz *650) /1000000000; br pwmSignal.pwmChannel = kPWM_PwmA; pwmSignal.level = kPWM_HighTrue; pwmSignal.dutyCyclePercent =30;/* x percent dutycycle */ pwmSignal.deadtimeValue = deadTimeVal; pwmSignal.faultState = kPWM_PwmFaultState0; pwmSignal.pwmchannelenable =true; br /*********** 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); } br /** * @brief 直流电机初始化,即 PWM1 的通道A和通道B初始化 * * @return int 0 on success, others on failure. */ intmotor_init(void) { pwm_config_tpwmConfig; pwm_fault_param_tfaultConfig; br pwmSourceClockInHz = PWM_SRC_CLK_FREQ; pwmFrequencyInHz = APP_DEFAULT_PWM_FREQUENCY; br SYSCON->PWM1SUBCTL |= SYSCON_PWM1SUBCTL_CLK0_EN_MASK;/* 只使能子模块0 */ br PWM_GetDefaultConfig(&pwmConfig); br pwmConfig.reloadLogic = kPWM_ReloadPwmFullCycle; /* use full cycle reload */ pwmConfig.pairOperation = kPWM_Independent; /* PWM A & PWM B operate as 2 independent channels */ pwmConfig.enableDebugMode =true; br if(PWM_Init(BOARD_PWM_BASEADDR, kPWM_Module_0, &pwmConfig) == kStatus_Fail) { PRINTF("PWM initialization failed "); return1; } br PWM_FaultDefaultConfig(&faultConfig); br #ifdefDEMO_PWM_FAULT_LEVEL faultConfig.faultLevel = DEMO_PWM_FAULT_LEVEL; #endif br /* 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); br /* 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); br /* Call the init function with demo configuration */ PWM_DRV_Init2PhPwm(); br /* Set the load okay bit for all submodules to load registers from their buffer */ PWM_SetPwmLdok(BOARD_PWM_BASEADDR, kPWM_Control_Module_0,true); br PWM_StartTimer(BOARD_PWM_BASEADDR, kPWM_Control_Module_0); br motor_stop(); br return0; }
motor_stop()
(滑动查看)
voidmotor_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)
(滑动查看)
/** *@brief正转 *@paramspeed 占空比,[0~100] */ voidmotor_left(uint32_t speed) { MOTOR_DIR_SET_LEFT(); m_motor_dir =MOTOR_DIR_LEFT; br 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)
(滑动查看)
/** * @brief 反转 * @param speed */ voidmotor_right(uint32_tspeed) { MOTOR_DIR_SET_RIGHT(); m_motor_dir = MOTOR_DIR_RIGHT; speed =100- speed; br 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); br PWM_StartTimer(BOARD_PWM_BASEADDR, kPWM_Control_Module_0); }
验证
01 示波器测量
上面黄色的是CH2,即PWM1_A信号(J3.15);
下面绿色的是CH1,即GPIO信号(J3.13);
电机正转motor left时CH1输出低电平;
电机反转motor right时CH1输出高电平。
motor stop
两路信号都是低电平。电机停转。
motor left 5
电机正转,速度为5。示波器观测PWM1_A占空比为5。
motor left 70
电机正转,速度为70。示波器观测PWM1_A占空比为70。
motor left 100
虽然PWM占空比可以设置成100%,但是两个高电平导致电机停转,所以这里把占空比合理的范围是[0,99]。
motor right 5
motor right 70
motor right 100
关于安富利
安富利是全球领先的技术分销商和解决方案提供商,在过去一个多世纪里一直秉持初心,致力于满足客户不断变化的需求。通过遍布全球的专业化和区域化业务覆盖,安富利可在产品生命周期的每个阶段为客户和供应商提供支持。安富利能够帮助各种类型的公司适应不断变化的市场环境,在产品开发过程中加快设计和供应速度。安富利在整个技术价值链中处于中心位置,这种独特的地位和视角让其成为了值得信赖的合作伙伴,能够帮助客户解决复杂的设计和供应链难题,从而更快地实现营收。