授權發明專利 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號