授权发明专利 35项 

支撑专利技术 30项

所有产品/方案可定制和二次开发 

  服务热线

  18688755863   产品购买

               13825202170     技术咨询

               18923482170     客户对接


蓝奥声科技单片机开发分层思维

作者:深圳蓝奥声科技有限公司 浏览: 发表时间:2021-01-05 17:57:15

以前通过接触公司里的产品,以及自己实际编写过的代码总结出一点分层编程的思维,在这里分享一下。首先分层思维,就是把整个工程按功能或控制分成几层(一般小的项目3到4层)以我这次做的蓝奥声智能硬件项目为例来讲解一下。


我负责的这个功能块是一个可以上升、下降的机械部件,这个机械部件叫“X管”。用直流电机驱动“X管”上升、下降,同时带动一个旋转编码器用于获取实时位置。当然还有一个原点检测开关。功能要求就是这个机械部件可以手动上下移动,以及通过两个“一键到位”按钮,让它移动到指定的两个位置。


首先介绍一下硬件。MCU是STM32F100,晶振 8.00M。直流电机驱动是一块蓝奥声科技PWM驱动板,驱动板有3个控制端口,两个逻辑端口控制电机正反转,一个PWM输入控制电机转速。编码器就是常见的欧姆龙增量式编码器,编码器的计数使用了 TIM3的编码器计数模式。


这个小项目我把他们分成了3层:


1层:硬件的配置(电机的控制I/O和编码器定时器模式及相关I/O的设定)和电器设备的基本控制(比如:电机的正反转,获取编码器的值,清空编码器值等)可以把这层理解为驱动层。


2层:具体某个功能的实现,“X管”的上升、下降,“X管”移动到指定的位置,以及“X管”的复位。


3层:状态层,怎么理解这层呢?比如说你按了一下“复位按钮”,那么“X管”的复位状态就置1,然后“X管”就执行“复位函数”自动去寻找原点。直到“X管”复位完成,程序自动清除复位状态,并且把已复位的状态置1(这个“已复位”状态也很关键,后面再讲)。

 

***层,硬件的配置和电器设备的基本控制:


1步:初始化电机的控制引脚、编码器输出引脚、PWM输出配置和定时器编码

器模式配置(其他的都很常见,只介绍定时器的编码模式)

 

u16 tim3_encoder_val = 0;    //TIM3 编码器模式计数值 encoder_val_ms

 

/*

普通定时器 TIM3 初始化为编码器模式

 明: 用作旋转编码器计数(欧姆龙 E6B2_CWZ6C

 数: psc 分频系数, arr 自动重载计数周期

返回值:

*/

void TIM3_EncoderInit(u16 psc, u16 arr)

{

GPIO_InitTypeDef        GPIO_InitStructure;        //GPIO参数

TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;    //定时器参数

TIM_ICInitTypeDef       TIM_ICInitStruct;          //编码器参数

NVIC_InitTypeDef        NVIC_InitStruct;           //中断 NVIC 参数

 

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);  //开启 PA 时钟

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);   //开启 TIM3 时钟

 

// A/ B

GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_6|GPIO_Pin_7;

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

//GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_IN_FLOATING;  //浮空输入

GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_IPU;            //上拉输入

GPIO_Init(GPIOA, &GPIO_InitStructure);

 

//定时器参数设置

TIM_DeInit(TIM3);                                              //把定时器寄存器的值设为默认值

TIM_TimeBaseStructInit(&TIM_TimeBaseInitStruct);               //用默认值填充指定的结构体

TIM_TimeBaseInitStruct.TIM_Prescaler     = (psc - 1);          //(168-1)设置分频系数

TIM_TimeBaseInitStruct.TIM_CounterMode   = TIM_CounterMode_Up; //计数方式(TIM9-TIM14只支持向上计数)

TIM_TimeBaseInitStruct.TIM_Period        = (arr - 1);          //自动重载计数周期

TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;       //时钟分频因子

TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStruct);

 

//编码器模式设置

TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Falling, TIM_ICPolarity_Falling); //下降沿

//TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI2, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);  //上升沿

TIM_ICStructInit(&TIM_ICInitStruct);                    //用默认值填充指定的结构体

TIM_ICInitStruct.TIM_ICFilter = 6;                      //指定输入捕获过滤器

TIM_ICInit(TIM3, &TIM_ICInitStruct);

 

//配置中断 NVIC 优先级分组

NVIC_InitStruct.NVIC_IRQChannel                   = TIM3_IRQn;

NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0x01;   //抢占有限级

NVIC_InitStruct.NVIC_IRQChannelSubPriority        = 0x01;   //响应优先级

NVIC_InitStruct.NVIC_IRQChannelCmd                = ENABLE; //使能中断

NVIC_Init(&NVIC_InitStruct);

 

TIM_ClearFlag(TIM3, TIM_FLAG_Update);             //清除TIM3(中断)的挂起标志

TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);        //开启TIM3的更新中断

 

TIM3 -> CNT = 0;                                  //清除定时器计数值

TIM_Cmd(TIM3, ENABLE);                            //开启定时器

 

tim3_encoder_val = TIM_GetCounter(TIM3);          //获取 TIM3 编码器计数值

//printf("编码器值:%u\r\n", tim3_encoder_val);

}

 

 

2步:电机驱动和编码器数值读取、写入(编码器省略)

 

/*

直流电机驱动

  明: 注意!!! 在进行大能量正反转切换时, PWM 占空比缓降为零(≥100ms)后再切换,否则可能造成驱动器损坏

  数: status 电机状态。 0停止 1正转 2反转

         speed 电机速度(0-1000

返回值:

*/

void DC_MotorDrive(u8 status, u16 speed)

{

    static u8 stop_sign = 0;  //停止标记(电机启动后必须停止 100ms 以上才可以换方向)

    static u8 dir_sign  = 0;  //电机转动方向标志(1正转  2反转)

    u16 pwm_val = 0;          //PWM 输出占空比    

        

    if(status==1 & dir_sign!=2)  //正转

    {

        dir_sign = 1;                   //标记电机转动方向

        stop_sign = 1;

        DC_MOTOR_FWD = 0;               //正转(有效)

        DC_MOTOR_REV = 1;               //反转(无效)

        TIM_SetCompare1(TIM1, speed);   //设置 PWM 占空比

        TIM_Cmd(TIM1, ENABLE);          //开启PWM定时器            

    }

    else if(status==2 & dir_sign!=1)  //反转

    {

        dir_sign = 2;

        stop_sign = 1;

        DC_MOTOR_FWD = 1;               //正转(无效)

        DC_MOTOR_REV = 0;               //反转(有效)

        TIM_SetCompare1(TIM1, speed); //设置 PWM 占空比

        TIM_Cmd(TIM1, ENABLE);

    }

    else if(status==0 | speed == 0)

    {

        dir_sign = 0;

        DC_MOTOR_FWD = 0;              //正转

        DC_MOTOR_REV = 0;              //反转(正反转都位低电平,电机制动)

        TIM_SetCompare1(TIM1, speed);

        

        if(stop_sign)

        {

            //printf("电机开始停止\r\n");

            delay_ms(200);

            stop_sign = 0;

            //printf("电机停止完成\r\n");

        }

        TIM_Cmd(TIM1, DISABLE);         //关闭 TIM1_PWM 定时器

    }    

}

 

第二层,功能层:“X管”的上升、下降,以及移动到指定位置

1步:电机复位,复位虽然并不复杂但却是电机控制中非常关键的一步,只有通过电机复位,才能确定“X管”***的零点位置,以后“X管”的限位,以及“X管”移动到指定的位置都是相对“X管”的零点位来确定的。

 

/*

球馆状态

pipe_status 球馆状态值

 

0:   球馆上升

1:   球馆下降

2:   球馆复位中...

3:   球馆自动移动中...

4-6: 保留

7:   球馆复位标志(0未复位  1已复位)

*/

vu8 pipe_status = 0;

 

u16 pipe_target = 0;   //球馆目标位置

 

/*

球馆复位

  明: 球馆移动到原点位置

  数: 无

返回值:

*/

void X_Pipe_Reset(void)

{

    static u8 status = 0;

    u16 site = 0;            //球馆上下位置

    

    if(!status)

    {

        if(DI_KEY & 0x10)  //不在零点

        {

            DC_MotorDrive(2, UP_SPEED_FAST);  //球馆上升

        }

        else  //回到零点

        {

            DC_MotorDrive(0, 0);        //球馆电机停止

            EncoderReset();             //编码器复位

            status = 1;

        }

    }

    

    if(status)  //已回零点

    {

        site = GetEncoder_val();        //获取电机当前位置

        

        if(site < 20)  //不在原点

        {

            DC_MotorDrive(1, DOWN_SPEED_FAST);  //球馆下降

        }

        else  //到达原点

        {

            DC_MotorDrive(0, 0);       //球馆拉杆电机停止

            status = 0;                //复位完成

            pipe_status &= ~0x04;      //复位状态清0

            pipe_status |= 0x80;       //复位标志置1

        }

    }

}

 

2步“X管”手动移动,这里只用上升来举例,下降同理

 

#define UP_LIMIT        20      //球馆上限位

#define DOWN_LIMIT      2500    //球馆下限位

 

/*

球馆上升

 明: 只有复位后,上限位才有效。

 数: speed 速度

返回值:

*/void X_Pipe_up(u16 speed)

{

    u16 site;

    

    if(pipe_status & 0x80)  //球馆已复位

    {

        site = GetEncoder_val();     //获取电机当前位置

        if(site > UP_LIMIT)

        {

            DC_MotorDrive(2, speed);  //直流电机反转

        }

        else

        {

            DC_MotorDrive(0, 0);     //球馆电机停止

        }

    }

    else  //没有复位,上限位无效

    {

        DC_MotorDrive(2, speed);     //直流电机反转

    }

}

 

3步“X管”移动到指定位置

/*

球馆移动到指定位置

 明: 指定位置是相对于零点位的***位置

 数: site ***位置,

site 0或超出限位,终止自动移动状态

返回值:

*/

void X_Pipe_Move(u16 site)

{

    static u8 status = 0;       //状态

    u16 site2 = 0;              //移动中位置  

    

    if(!(pipe_status & 0x80))  //球馆未复位

    {

        status = 0;

        pipe_status &= ~0x08;  //球馆移动状态清0

        return;                //退出(这里有多个判断代码段,每个判断段里执行的程序差不多,可以用goto语句优化一下)

    }

 

    if(site>=DOWN_LIMIT || site<=UP_LIMIT) //目标位置超出限位范围

    {

        DC_MotorDrive(0, 0);   //球馆电机停止

        status = 0;            //状态清0

        pipe_status &= ~0x08;  //球馆移动状态清0

        return;                //退出

    }

    

    site2 = GetEncoder_val();    //获取电机位置

    

    if(site2>=DOWN_LIMIT || site2<=UP_LIMIT) //当前位置超出限位范围

    {

        DC_MotorDrive(0, 0);   //球馆电机停止

        status = 0;

        pipe_status &= ~0x08;

        return;

    }

    

    if(!status)

    {

        if(site > site2)

            status = 1;       //球馆向下移动

        else if(site < site2)

            status = 2;       //球馆向上移动

        else

        {

            DC_MotorDrive(0, 0);   //球馆电机停止

            status = 0;

            pipe_status &= ~0x08;

            return;

        }

    }

    

    if(status == 1)  //球馆向下移动(反转)

    {

        if((site-site2) > 50)  //快速移动

        {

            X_Pipe_down(DOWN_SPEED_FAST);          //球馆下降

        }

        else  //慢速移动

        {

            if(!(site2>=site))

            {

                X_Pipe_down(DOWN_SPEED_FAST);     //球馆慢速下降

            }

            else  //到达目标位

            {

                DC_MotorDrive(0, 0);   //球馆电机停止

                status = 0;

                pipe_status &= ~0x08;  //球馆移动状态清0

            }

        }

    }

    else if(status == 2)  //向上移动(正转)

    {

        if((site2-site) > 50)  //快速移动

        {

            X_Pipe_up(DOWN_SPEED_FAST);        //球馆上升

        }

        else  //慢速移动

        {

            if(!(site>=site2))

            {

                X_Pipe_up(DOWN_SPEED_SLOW);     //球馆慢速上升

            }

            else  //到达目标位

            {

                DC_MotorDrive(0, 0);   //球馆电机停止

                status = 0;

                pipe_status &= ~0x08;  //球馆移动状态清0

            }

        }

    }

}

 

第三层,状态层:根据状态自动运行(相应的按钮操作部分很常见,也很简单所以就省略这部分代码)

说明:下面的这个“状态函数”是工程中的一个任务,还有“按钮扫描”等其他任务,下面举了两个列子来说明。


1.假如有人按了一下“复位”按钮,“按钮扫描”函数仅仅把复位状态置1,状态函数根据状态值自动执行相应的函数。


2.按了一下“一键到位”按钮,“按钮扫描”同样把自动移动状态置1,并把一键到位的位置值赋给 pipe_target 变量。状态函数根据状态值自动执行相应的函数。

 

/*

设备状态

 明: 根据设备的状态执行相应的程序

 数:

返回值:

*/

void XH_status(void)

{

if(pipe_status & 0x04)       //球馆复位状态置1

{

X_Pipe_Reset();          //球馆复位

}

else if(pipe_status & 0x08)  //球馆自动移动状态置1

{

X_Pipe_Move(pipe_target); //球馆移动到指定位置

}

 

if(!(pipe_status & 0x7F))    //球馆状态为0

{

DC_MotorDrive(0, 0);     //球馆电机停止

}

}

 

好了,以上就是我要分享的分层思维,以及具体的实现过程,欢迎大家指点。


文章推荐
图片展示
公众号
在线咨询

您好,请点击在线客服进行在线沟通!

联系方式
热线电话
18688755863
上班时间
周一到周六
E-mail地址
liangjingshan@alm-iot.cn
扫一扫二维码
二维码
添加微信好友,详细了解产品
使用企业微信
“扫一扫”加入群聊
复制成功
添加微信好友,详细了解产品
我知道了
粤ICP备14082221号