STM32F103RCT6的PWM波形图能否在PC0引脚输出???

& & 通过前面几步练习,我已经能顺畅的和外界交流了,而且可在活动状态下不受限制的接收命令、返回状态。
& & &现在要开始活动了,首先要能使唤我的&腿脚&,这一步就开始练习驱动我的动力源:电机,当然还是基于 uCOS 去实现。
6.1 硬件资源回顾
& & &为了&围观&者能看明白,还是先看看我的硬件资源吧。
& & &在前面&秀身材&时已经将我的身体抽象成如下&黑箱&:
& & &因这一步只是让我&动&起来,所以暂不关注我的检测信号,只看我的电机驱动接口。
& & &我的&身体&左右各一组6芯的控制线:
& & &G && 电源地线;
& & &BAT && 电机驱动 H 桥供桥电压输入 ,4 - 6V(正极);
& & &CT1 && 电机控制线 CTRL1,电机控制 PWM 信号;
& & &CT2 && 电机控制线 CTRL2,电机运行状态控制,与 CTRL3配合;
& & &CT3 && 电机控制线 CTRL3,电机运行状态控制,与 CTRL2配合;
& & &VCC && H 桥驱动逻辑电路及电流、电压检测电路的供电电压(正极)。
& & &里面的细节不用去管,按照下面的表给我信号就可以让我动起来:
表1 电机控制状态表
& & &在我的&大脑&&& STM32控制板上,和电机驱动相关的资源有:
& & &左侧电机驱动用:
& & &Ctrl-L1 && PA2,Alternate function push-pull,电机PWM控制,可用T2CH3产生PWM。
& & &Ctrl-L2 && PA1,Output push-pull,电机工作状态控制,详见表1中的控制逻辑。
& & &Ctrl-L3 && PA0,Output push-pull, 电机工作状态控制,详见表1中的控制逻辑。
& & &右侧电机驱动用:
& & &Ctrl-R1 && PA3,Alternate function push-pull,电机PWM控制,可用T2CH4产生PWM。&
& & &Ctrl-R2 && PC8,Output push-pull,电机工作状态控制,详见表1中的控制逻辑。
& & &Ctrl-R3 && PC7,Output push-pull, 电机工作状态控制,详见表1中的控制逻辑。
& & &下面开始用 uCOS &神功&实现电机驱动,使我的&腿脚&可以动起来。
6.2 实现构思
& & &因为是练习 uCOS,还是采用任务模式。
& & &电机驱动要完成的是:接受命令,根据命令驱动电机,使之工作于:正转、反转、惰行、刹车四个状态之一。在正、反转时,还要实现 PWM 方式调功,使电机可以改变速度。
& & &因为我的行动是靠两侧的电机运动组合实现的,单独控制一侧无法有效的产生行为(前进、后退、转向),所以将左、右侧电机驱动合并,创建一个&电机驱动任务&。
& & &因为电机驱动属于执行层,通常是接受其它任务的调遣,所以其优先级无需太高,暂时设置为 25 ,略高于 USART1 的接收和发送任务,目的是避免被串口通讯任务抢占,导致&行动&响应滞后太多。
& & &因电机驱动不需要保存历史命令,只要响应最新的操作即可,所以无需像通讯的发送任务那样采用队列方式传递数据,用 uCOS 的邮箱 MailBox(又学了一个uCOS新功夫)即可,电机驱动任务只需根据最新的邮件动作。
& & &因为邮箱机制不允许覆盖,所以在发送邮件前用 OSMboxAccept 函数清空邮箱。
& & &为了简化电机控制所需传递的信息,将电机的4个工作状态(正转、反转、惰行、刹车)以及转动的 PWM 值简化为一个32位有符号整形数值,对应左、右侧两个电机,定义如下数据结构:
typedef struct
& INT32S & & & & & &s32LeftPwmV
& INT32S & & & & & &s32RightPwmV
}MotorCtrlC & &&
& & &用符号表示正、反转,定义以下特殊值对应另外其它特殊状态:
& & &#define & & & & & & BRAKE_PWM & & & & & 0x10000 & & & & & & // 刹车
& & &#define & & & & & & FLOAT_PWM & & & & & &0 & & & & & & & & & // 惰行
& & &将上述数据结构作为邮件信息格式。
& & &邮箱信息由控制电机的任务发出,电机驱动任务平时处于等待状态,收到邮件后设置相应参数。
& & &电机驱动的核心是 PWM 的产生方式,PWM 的频率和电机的特性相关。
(旁白: 关于电机的PWM频率)
& & &在这一点上我走过不小的弯路,原来由于实验不严谨,基于用低频率的PWM,50%占空比时反而比高频率转的更快的现象,误认为电机的 PWM 频率不需要太高,并参考 LEGO 电机的 PWM 驱动频率,选择了125Hz。
& & &但随着应用的深入,在尝试 PID 调速时遇到了麻烦,一直无法达到理想的速度,低速时转速总是波动幅度很大。
& & &无奈仔细研究电机的驱动,换了不同特性的电机,似乎没有太大的改观。
& & &最后将关注点放在了电机&断流&的概念上,按书上所说,PWM 频率过低会导致电机&断流&,即没有驱动电压时,电流也回零了。
& & &而&续流&是靠电枢的电感产生的,由于电枢电感很小,所以续流时间也很短。
& & &为此,我在电机回路串联了一个0.22欧姆电阻,用示波器测量电阻上的电压得到电机电流,仔细用示波器测量后发现&断流&现象确实存在。
& & &而断流就意味着电机失去动力,此时,如果转动部分的机械惯量较大,还可以维持转动的平滑,否则就会产生转速波动,如果再有阻力,那波动将会更加明显。
& & &以下是在125Hz下驱动130电机的波形(图中黄色为PWM驱动波,绿色为0.22欧姆上的电压,最下面的绿色线为电压零线,下同):
PWM125Hz下 25%占空比波形
PWM125Hz下 50%占空比波形
PWM125Hz下 75%占空比波形
& & &&从图中可以看出,三种情况下均出现&断流&现象(电阻上电压降为&0&)。
& & &&FIRA 小车的电机 PWM&频率提升到 10kHz 后,断流现象基本消失,电流呈三角波,但未回零:
PWM10kHz下 25%占空比波形
PWM10kHz下 50%占空比波形
PWM10kHz下 75%占空比波形
& & &由此可以看出:电机应该驱动更加平稳。看来电机的PWM频率确实是越高越好啊!
& & &PWM 频率提高的障碍通常是硬件,主要是 MCU 的能力。STM32 的定时器硬件 PWM 输出功能完全可以胜任,所以此处设计用的就是 Timer2 的 PWM 输出。
(旁白完)
& & &PWM的工作模式设计为:用定时器的Counter 减计数产生 PWM频率,利用2个通道的&PWM 输出模式&产生占空比可变的脉冲。
& & &Timer1 比较高级,有些难于理解,暂不使用,启用Timer2。
& & &其计数频率设置为 72MHz,为达到尽量高的 PWM 分辨率,设置预分配数为 1,Counter 的自动重加载值为 7200,减计数。PWM 的分辨率达到 13 位,PWM 频率 10kHz。
& & &左侧电机就使用 T2CH3,右侧电机使用 T2CH4。两个通道均设置为 PWM 输出模式 1。Counter 回零重加载时, PWM 输出无效电平,减计数至比较值后PWM输出有效电平。这样设定的&比较值除重加载值&就是PWM占空比,即:
& & &左侧电机 PWM = TIM2_CCR3/7200
& & &右侧电机 PWM = TIM2_CCR4/7200
& & &上述PWM输出处理 Timer2 均可自动完成。
& & &电机驱动任务除负责接受控制命令外,为增加电机运转的可靠性,增加一个保护处理:根据命令判断电机是否要变换转动方向,如果是,则提供保护逻辑,插入&刹车&,避免直接转换方向带来的电流冲击。这算是一个小噱头吧 ^_^
6.3 程序构建
& & &第一步:初始化硬件
& & &电机驱动涉及的硬件有 6 个 I/O 输出口和1个定时器。
& & &在 BSP 中初始化如下:
& & &首先,在 BSP.H 中定义:
/* 电机驱动用 IO 口 */
#define & & & BSP_GPIOA_CTRL_L1 & & & & GPIO_Pin_2 & & & & & & & // 140127
#define & & & BSP_GPIOA_CTRL_L2 & & & & GPIO_Pin_1 & & & & & & & // 140127
#define & & & BSP_GPIOA_CTRL_L3 & & & & GPIO_Pin_0 & & & & & & & // 140127
& & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & &
#define & & & BSP_GPIOA_CTRL_R1 & & & & GPIO_Pin_3 & & & & & & & // 140127
#define & & & BSP_GPIOC_CTRL_R2 & & & & GPIO_Pin_8 & & & & & & & // 140127
#define & & & BSP_GPIOC_CTRL_R3 & & & & GPIO_Pin_7 & & & & & & & // 140127
& & &在BSP.C 中初始化相应的硬件:
void BSP_MotorDrv_Init(void)
& GPIO_InitTypeDef GPIO_InitS
& TIM_TimeBaseInitTypeDef &TIM_TimeBaseS
& TIM_OCInitTypeDef TIM_OCInitS
& /* Enable GPIOA GPIOC clock */
& RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOC, ENABLE); & &
& /* 配置左、右电机驱动 IO 口为推挽输出 &*/
& GPIO_InitStructure.GPIO_Pin = BSP_GPIOA_CTRL_L1|BSP_GPIOA_CTRL_R1;
& GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
& GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
& GPIO_Init(GPIOA, &GPIO_InitStructure);
& GPIO_InitStructure.GPIO_Pin = BSP_GPIOA_CTRL_L2|BSP_GPIOA_CTRL_L3;
& GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
& GPIO_Init(GPIOA, &GPIO_InitStructure);
& GPIO_InitStructure.GPIO_Pin = BSP_GPIOC_CTRL_R2|BSP_GPIOC_CTRL_R3;
& GPIO_Init(GPIOC, &GPIO_InitStructure);
& /* 初始化 Timer2 ,使用CH3 Ch4 PWM 输出模式1*/
& /* TIM2 clock enable */
& RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
& /* &复位Timer2 &*/
& TIM_DeInit(TIM2);
& /* Time base configuration */
& TIM_TimeBaseStructure.TIM_Period = BSP_TIMER2_RELOAD_VAL;
& TIM_TimeBaseStructure.TIM_Prescaler = BSP_TIMER2_CNT_PRE_DIV;
& TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
& TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_D
& TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
& TIM_ARRPreloadConfig(TIM2, ENABLE);
& /* PWM2 Mode configuration: Channel3 */
& TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; & & & & & &// 减计数,小于比较值输出有效
& TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_E
& TIM_OCInitStructure.TIM_Pulse = BSP_TIMER2_PWM_INACTIVE;
& TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_H & &// 输出高电平有效
& TIM_OC3Init(TIM2, &TIM_OCInitStructure);
& TIM_OC3PreloadConfig(TIM2, TIM_OCPreload_Enable);
& TIM_OC4Init(TIM2, &TIM_OCInitStructure);
& TIM_OC4PreloadConfig(TIM2, TIM_OCPreload_Enable);
& /* TIM IT enable */
& TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
& /* TIM2 enable counter */
& TIM_Cmd(TIM2, ENABLE);
& /* 在 uCOS 中注册中断 */
& TIM_ClearITPendingBit(TIM2, TIM_IT_Update); & & & & & & & // 打开中断前**所有标志
& TIM_ClearITPendingBit(TIM2, TIM_IT_CC1);
& TIM_ClearITPendingBit(TIM2, TIM_IT_CC2);
& TIM_ClearITPendingBit(TIM2, TIM_IT_CC3);
& TIM_ClearITPendingBit(TIM2, TIM_IT_CC4);
& BSP_IntVectSet(BSP_INT_ID_TIM2,BSP_Timer2_Int); & & & & & // 将中断服务程序写入系统的中断程序表
& BSP_IntEn(BSP_INT_ID_TIM2); & & & & & & & & & & & & & & & // 允许中断,设置 NVIC
& & &注意,在上述程序中将电机驱动所需的所有硬件初始化集中在一起处理!我觉得这样处理可读性好,关键是易于移植,不至于东一处、西一处的导致遗漏。
& & &第二步:定义任务、邮箱及全局变量
void & & & & & & & &App_TaskMotorDrv & &(void *p_arg); & & &// 电机驱动任务
OS_EVENT & & & & &*App_MotorDrvMailB & & & & & & & & && &// 定义电机驱动数据邮箱&
MotorCtrlCommand & g_MotorCtrlC & & & & & & & & & &// 电机控制命令
MotorCtrlPara & & & &g_LeftM & & & & & & & & & & & & & // 左侧电机控制参数
MotorCtrlPara & & & &g_RightM & &
& & &电机控制参数结构定义:
typedef struct
& INT8U & & & & & & u8InsertB
& INT8U & & & & & & u8MotorS
& INT16U & & & & & &u16PwmV
}MotorCtrlP & & & & & & & & &// 根据电机控制命令解析后得到的电机控制参数,据此产生实质控制
& & &其它任务向电机驱动任务传递消息则采用 uCOS 提供的邮箱。
& & &第三步:编写中断服务程序
& & &因为PWM输出由Timer2硬件产生,定时器中断暂时没有处理。
& & &第四步:编写电机驱动任务
& & &首先完成相关初始化,然后进入等到邮件状态;收到邮件后,根据邮件内容设置电机工作状态(正转、反转、惰行、刹车)和PWM值,之后再次进入等待。维持电机工作的任务实际上是硬件完成的。
void & App_TaskMotorDrv(void *p_arg)
& MotorCtrlCommand *p_CtrlC
& MotorCtrlCommand CtrlC
& (void)p_
& /* 初始化相关变量 */
& g_LeftMotor.u8InsertBrake = FALSE;
& g_LeftMotor.u16PwmVal = BSP_TIMER2_PWM_INACTIVE;
& g_LeftMotor.u8MotorStat = FLOAT;
& g_RightMotor.u8InsertBrake= FALSE;
& g_RightMotor.u16PwmVal = BSP_TIMER2_PWM_INACTIVE;
& g_RightMotor.u8MotorStat = FLOAT;
& BSP_MotorDrv_Init();
& BSP_DriveMotor(LEFT_MOTOR,FLOAT,BSP_TIMER2_PWM_INACTIVE);
& BSP_DriveMotor(RIGHT_MOTOR,FLOAT,BSP_TIMER2_PWM_INACTIVE);
& App_MotorDrvMailBox = OSMboxCreate(NULL);
& while(1)
& & p_CtrlCommand = OSMboxPend(App_MotorDrvMailBox,0,&err); // 等待命令
& & CtrlCommand = *p_CtrlC & & & & & & & & & & & & & &// 保存命令
& & g_MotorCtrlCommand = CtrlC & & & & & & & & & & & // 为便于用读内存命令读取
& & /*---------- 处理两侧电机
----------------*/
& & setMotorPara(CtrlCommand.s32LeftPwmVal,&g_LeftMotor); & & // 根据命令设定控制参数
& & setMotorPara(CtrlCommand.s32RightPwmVal,&g_RightMotor); &
& & if((g_LeftMotor.u8InsertBrake)&&(g_RightMotor.u8InsertBrake))
& & & //------ 情况1:左右均直接改变方向,插入刹车处理 ---------
& & & BSP_DriveMotor(LEFT_MOTOR,BRAKE,g_LeftMotor.u16PwmVal); // 输出左侧刹车控制
& & & BSP_DriveMotor(RIGHT_MOTOR,BRAKE,g_RightMotor.u16PwmVal); &// 输出右侧刹车控制
& & & OSTimeDly(INSERT_BRAKE_TIME); & & & & & & & & & & & & & & & // 延时,暂时将MCU控制权交出
& & & setMotorPara(CtrlCommand.s32LeftPwmVal,&g_LeftMotor); & & & & & // 再次根据左侧命令设定控制参数
& & & setMotorPara(CtrlCommand.s32RightPwmVal,&g_RightMotor); & & & &// 再次根据右侧命令设定控制参数
& & & BSP_DriveMotor(LEFT_MOTOR,g_LeftMotor.u8MotorStat,g_LeftMotor.u16PwmVal);// 输出左侧转动控制
& & & BSP_DriveMotor(RIGHT_MOTOR,g_RightMotor.u8MotorStat,g_RightMotor.u16PwmVal);// 输出右侧转动控制
& & & if((g_LeftMotor.u8InsertBrake==FALSE)&&(g_RightMotor.u8InsertBrake==FALSE))
& & & & //------ 情况2:左右电机均未改变方向, 正常输出 -------
& & & & BSP_DriveMotor(LEFT_MOTOR,g_LeftMotor.u8MotorStat,g_LeftMotor.u16PwmVal); // 输出左侧转动控制
& & & & BSP_DriveMotor(RIGHT_MOTOR,g_RightMotor.u8MotorStat,g_RightMotor.u16PwmVal);//右侧转动控制
& & & else
& & & & if(g_LeftMotor.u8InsertBrake)
& & & & & // ---- 情况3:左侧方向改变,插入刹车,右侧正常输出 -----
& & & & & BSP_DriveMotor(LEFT_MOTOR,BRAKE,g_LeftMotor.u16PwmVal); & // 输出左侧刹车控制
& & & & & BSP_DriveMotor(RIGHT_MOTOR,g_RightMotor.u8MotorStat,g_RightMotor.u16PwmVal);//右侧转动控制
& & & & & OSTimeDly(INSERT_BRAKE_TIME); & & & & & & & & & & & // 延时,暂时将MCU控制权交出
& & & & & setMotorPara(CtrlCommand.s32LeftPwmVal,&g_LeftMotor); & // 再次根据左侧命令设定控制参数
& & & & & BSP_DriveMotor(LEFT_MOTOR,g_LeftMotor.u8MotorStat,g_LeftMotor.u16PwmVal);// 输出左侧转动控制
& & & & else
& & & & & // ---- 情况4:右侧方向改变,插入刹车,左侧正常输出 -----
& & & & & BSP_DriveMotor(RIGHT_MOTOR,BRAKE,g_RightMotor.u16PwmVal); & & // 输出右侧刹车控制
& & & & & BSP_DriveMotor(LEFT_MOTOR,g_LeftMotor.u8MotorStat,g_LeftMotor.u16PwmVal);//输出左侧转动控制
& & & & & OSTimeDly(INSERT_BRAKE_TIME); & & & & & & & & & // 延时,暂时将MCU控制权交出
& & & & & setMotorPara(CtrlCommand.s32RightPwmVal,&g_RightMotor); &// 再次根据右侧命令设定控制参数
& & & & & BSP_DriveMotor(RIGHT_MOTOR,g_RightMotor.u8MotorStat,g_RightMotor.u16PwmVal);//右侧转动控制
& & &因为设置电机工作状态和硬件强相关,故将其作为 BSP 函数,内容很简单,就是用 STM32 库函数中的I/O口位操作命令设置相应的输出端即可。
& & &我的两条腿必须同步控制,如果有明显的先后,则会导致我走偏,所以在上述控制中特别注意了两侧输出的同时性,避免产生较大的先后差异。
& & &为了增加点噱头,在将电机 PWM 命令转化为电机工作状态和 PWM 值过程中,增加了一点&智能&,因为电机直接从正转到反转电流会很大,故人为插入一个刹车状态,不一定实用,供参考。
& & &注意:前面变量定义中的&&g_LeftMotor 及 g_RightMotor 的数据类型。这样用指针即可将参数传入函数。
因为上述逻辑比较麻烦,所以设计了一个函数以改善主程序的可读性。
& & &函数根据收到的电机控制命令 CtrlCommand,结合当前电机状态,转换为电机的具体操作参数 g_LeftMotor 及 g_RightMotor,也就是上述数据结构 MotorCtrlPara 中的三个电机工作参数。
& & &函数如下:
void setMotorPara(INT32S s32CtrlCommand, MotorCtrlPara *p_CtrlPara)
& INT32S &s32T
& INT16U &u16T
& INT8U & u8ProcP
& INT8U & u8MotorS
& if(s32CtrlCommand & 0)
& & if(p_CtrlPara-&u8MotorStat == FORWARD)
& & & /* 从前进直接转为后退,插入刹车状态 */
& & & p_CtrlPara-&u8InsertBrake = TRUE;
& & & p_CtrlPara-&u8MotorStat = BRAKE;
& & & p_CtrlPara-&u16PwmVal = BSP_TIMER2_PWM_INACTIVE;&
& & & u8ProcPwm = FALSE;
& & & /* 进入后退状态 */
& & & s32Temp = 0 - s32CtrlC & & & & & & & & & & & & & & & & & & & // 转换为正值
& & & u16Temp = (INT16U)s32T
& & & u8MotorStat = BACKWARD;
& & & u8ProcPwm = TRUE; & & & & & & & & & & & & & & & & & & & & & & & &// 和前进状态一起处理 PWM
& & switch(s32CtrlCommand)
& & & case FLOAT_PWM:
& & & & if(p_CtrlPara-&u8MotorStat == FLOAT)
& & & & & /* 状态不改变 */
& & & & & p_CtrlPara-&u8InsertBrake = FALSE;
& & & & & p_CtrlPara-&u16PwmVal = BSP_TIMER2_PWM_INACTIVE;&
& & & & & u8ProcPwm = FALSE;
& & & & else
& & & & & /* 进入惰行状态 */ & & & & & & & &
& & & & & p_CtrlPara-&u8InsertBrake = FALSE;
& & & & & p_CtrlPara-&u8MotorStat = FLOAT;
& & & & & p_CtrlPara-&u16PwmVal = BSP_TIMER2_PWM_INACTIVE;&
& & & & & u8ProcPwm = FALSE;
& & & case BRAKE_PWM:
& & & & if(p_CtrlPara-&u8MotorStat == BRAKE)
& & & & & /* 状态不改变 */
& & & & & p_CtrlPara-&u8InsertBrake = FALSE;
& & & & & p_CtrlPara-&u16PwmVal = BSP_TIMER2_PWM_INACTIVE;&
& & & & & u8ProcPwm = FALSE;
& & & & else
& & & & & /* 进入刹车状态 */ & & & & & & & &
& & & & & p_CtrlPara-&u8InsertBrake = FALSE;
& & & & & p_CtrlPara-&u8MotorStat = BRAKE;
& & & & & p_CtrlPara-&u16PwmVal = BSP_TIMER2_PWM_INACTIVE;&
& & & & & u8ProcPwm = FALSE;
& & & default:
& & & & if(p_CtrlPara-&u8MotorStat == BACKWARD)
& & & & & /* 从后退直接转为前进,插入刹车状态 */
& & & & & p_CtrlPara-&u8InsertBrake = TRUE;
& & & & & p_CtrlPara-&u8MotorStat = BRAKE;
& & & & & p_CtrlPara-&u16PwmVal = BSP_TIMER2_PWM_INACTIVE;&
& & & & & u8ProcPwm = FALSE;
& & & & else
& & & & & /* 进入前进状态 */
& & & & & u16Temp = (INT16U)s32CtrlC
& & & & & u8MotorStat = FORWARD;
& & & & & u8ProcPwm = TRUE; & & & & & & & & & & & & & & & & & & & & & // 和后退状态一起处理 PWM
& if(u8ProcPwm)
& & if((u8MotorStat == p_CtrlPara-&u8MotorStat)&&(u16Temp == p_CtrlPara-&u16PwmVal))
& & & /* &状态不变 */
& & & p_CtrlPara-&u8InsertBrake = FALSE;
& & & /* 状态改变 */
& & & p_CtrlPara-&u8InsertBrake = FALSE;
& & & p_CtrlPara-&u8MotorStat = u8MotorS & & &
& & & p_CtrlPara-&u16PwmVal = u16T&
& & &至此,电机驱动任务已完成,只需将左右电机的PWM值通过邮件发送给电机驱动任务即可操作电机。
& & &第五步:在通讯任务中增加电机测试命令: MOTOR_PWM_CTRL
& & &为了让我动起来,在通讯协议中增加一个电机 PWM 控制命令,补充定义如下:
& & &命令三:电机PWM控制,实现左右电机的PWM控制,包括正、反转、惰行、刹车。
& & &命令字 &&& 0x03
& & &数据域 &&& 左侧电机PWM(1字节)右侧电机PWM(1字节)
& & &电机PWM值定义为:
& & & & &&1 字节有符号数,PWM 值为 0 -100,负数为反转。127为刹车,0为惰行。
& & &返回数据帧为:
& & & & &&帧头 &发送方地址 自己的地址 &帧长 &命令 &校验和
& & &在命令解析函数 do_Command中增加:
& & case &MOTOR_PWM_CTRL:
& &&/* 电机 PWM 控制 */
& & & i = p_CurComPort-&i_u8StartP
& & & for(j = 0; j & 2; j++)
& & & &&i = (i + 1)&(APP_USART_RCVBUF_LOOP); &&
& & & & s8PWMCommand = p_CurComPort-&pa_u8Buf[i]; & & & & & & & // 取电机 PWM
& & & & if(s8PWMCommand == BRAKE_COMMAND)
& & &&& & &&s32Temp.all = BRAKE_PWM;
& & & & else
& & &&& & &&s32Temp.all = PWM_100/PWM100_COMMAND * s8PWMC & & & & &
& & & & for(n = 0; n & 4; n++)
& & & & & *p_u8Data = s32Temp.b[n]; & & & & & & & & & & & & & & & &// 保存电机控制命令&
& & & & & p_u8Data++;
& & & /* 准备发送消息 */
& & & *p_u8Send = 1; & & & & & & & & & & & & & & & & & & & & & & & & &// 1字节命令
& & & p_u8Send++;
& & & *p_u8Send = MOTOR_PWM_CTRL|ANSWER; & & & & & & & & & & & &&// 返回命令
& & & u16OpFlag = SEND_DATA_OP|MOTOR_DRV_OP; & & & & & & & & & &// 置操作标志&
& & &对应的在 USART接收任务App_TaskUSART1Rcv 中添加:
if((u16OpFlag & MOTOR_DRV_OP) == MOTOR_DRV_OP) & & & & & &&
& &p_CtrlCommand = (MotorCtrlCommand *)(p_u8MemStart + APP_MOTORDRV_DATA_OFFSET);
& &CtrlCommand = *p_CtrlC & & & & & & & & & & & & & &// 提取数据
& &OSMboxAccept(App_MotorDrvMailBox); & & & & & & & & & & &// 清空邮箱&
& &err = OSMboxPost(App_MotorDrvMailBox,&CtrlCommand); // 发送邮件给电机控制任务
& & &这样,就可以很方便的用PC通过串口测试电机驱动功能了。
& & &因无线通讯的命令解析和 USART 共享,所以只需在无线接收任务 App_TaskSi4432Rcv 中添加和 USART接收任务类似的操作即可实现无线方式控制电机:
f((u16OpFlag & MOTOR_DRV_OP) == MOTOR_DRV_OP)
& & p_CtrlCommand = (MotorCtrlCommand *)(p_u8MemStart + APP_MOTORDRV_DATA_OFFSET);
& & CtrlCommand = *p_CtrlC & & & & & & & & & & & & & & & & & & & & // 提取数据
& & OSMboxAccept(App_MotorDrvMailBox); & & & & & & & & & & & & & & & & & // 清空邮箱&
& & err = OSMboxPost(App_MotorDrvMailBox,&CtrlCommand); & & & & & & & // 发送邮件给电机控制任务,
& & &可以看出,两个任务处理方式是一样的。为了调试直观、方便,还是需要编写相应的PC侧程序。
& & &第六步:构建PC上的测试程序
& & &为简化编程,共享原来的收、发程序,直接在前面所做的读内存程序基础上添加:
& & &因为 PC 程序只是作为辅助手段,不属于我&练功&的范畴。故在此不做深入探讨,有兴趣者自己阅读所附程序。
& & &同样原因,也编写了一个基于Processing的测试程序,界面如下:
& & &模仿Delphi的页面模式,在原来的内存读写测试程序基础上,增加了电机PWM测试功能,将两个测试功能分成两个页面,对应两个独立的处理文件,这样程序有较好的可扩展性,目前修改后的程序很容易增加功能,不用像原来VC程序那样将所有测试功能挤在一个界面中(我不会用VC编写生成新窗口,不是VC的问题,而是我的水平有限),因为原来VC中生成新的窗口有点复杂。
& & &但用 Processing 生成页面切换完全是我程序逻辑所产生的,无需借用任何&不知底细&的元素,掌控性更好,至少我是这么认为!
& & &经过上述努力,我的腿脚已可以运动自如了,借助PC调试程序,可以随心所欲的让我的&腿脚&运动和停止。
& & &&腿脚&能动了,也就存在&受伤&的可能。为保证我的安全,需要增加一些保护措施,以使我的运动更加可靠。
& & &最有可能的损坏就是因电机电流过大而损坏驱动电路。电机如果在运动中卡死,就会出现大电流(学名:堵转电流)。如果供给电机的电压太低,将导致动力不足,更容易卡死,从而导致大电流。所以需要测试电机的供电电压和工作电流,下一步的练习就是如何准确的测量我的电机工作电压和电流。能真实的测到电压、电流,才有可能提供保护。
& & &作为 uCOS 的练习,邮箱的使用很顺利,而且不是很难;利用邮箱机制传递消息直观,易于理解。这应该就是基于OS编程的优点吧!
& & &除了邮箱,本练习还利用了 uCOS 的延时功能,十分容易的实现了电机换向时插入刹车的处理。如果没有uCOS 提供的系统延时函数,这个操作不是导致其它任务失步,就是处理十分繁琐。而利用系统的延时函数,很简洁的就完成了:&
&& & &BSP_DriveMotor(LEFT_MOTOR,BRAKE); & & & & & & & & & & & & &// 输出电机控制信号
& & & OSTimeDly(INSERT_BRAKE_TIME); & & & & & & & & & & & & & & & // 延时,暂时将MCU控制权交出
& & & setMotorPara(CtrlCommand.s32LeftPwmVal,&g_LeftMotor); &// 再次根据命令设定控制参数
& & &注意:利用 OSTimeDly(INSERT_BRAKE_TIME),既完成了延时,又不至于占用 MCU,导致其它任务无法处理。如不使用 OS,这段处理将会涉及很多部分,程序难写,更难读。
& & &涉及到硬件,通过将其操作封装,可以大大改善程序的可读性。所谓&与硬件无关的编程&方式,估计其精髓也在于此:将与硬件相关的处理、操作封装成函数,提供简单明了的接口参数。本练习中涉及电机的操作函数为:
& & &初始化函数:BSP_MotorDrv_Init()
& & &参数转换函数:setMotorPara(INT32S s32CtrlCommand, MotorCtrlPara *p_CtrlPara)
& & &电机驱动函数:BSP_DriveMotor (INT8U u8MotorFlag, INT8U u8MotorStat)
& & &通过这些函数将需要硬件知识的一些操作简化了。
&&&&&&&&&&&&
注:第五步尝试的程序(含 VC6.0 和 Processing 程序)在 QQ 群& 共享文件中,供参考。欢迎交流、讨论!
参考资料:
1、《嵌入式实时操作系统 uC/OS-II(第二版)》邵贝贝 等译 ISBN7-
2、《基于嵌入式实时操作系统的程序设计技术》周航慈 吴光文著 ISBN978-7-
3、《Cortex-M3 + uCOS-II嵌入式系统开发入门与应用》陈瑶等著 ISBN978-7-115-23105-5
4、《uC/OS-II 标准教程》杨宗德 张兵 著 ISBN978-7-115-20442-4
5、uC/OS-II 官方网站资料
6、STM32LIB V 3.5 库例程和手册
7、STM32数据手册
与非门科技(北京)有限公司 All Rights Reserved.
京ICP证:070212号
北京市公安局备案编号: 京ICP备:号

我要回帖

更多关于 波形护栏多少钱一米 的文章

 

随机推荐