新聞中心

        EEPW首頁 > 嵌入式系統 > 設計應用 > 第25節:用LED燈和按鍵來模擬工業自動化設備的運動控制

        第25節:用LED燈和按鍵來模擬工業自動化設備的運動控制

        作者: 時間:2016-11-22 來源:網絡 收藏
        開場白:
        前面三節講了獨立按鍵控制跑馬燈的各種狀態,這一節我們要做一個機械手控制程序,這個機械手可以左右移動,最左邊有一個開關感應器,最右邊也有一個開關感應器。它也可以上下移動,最下面有一個開關感應器。左右移動是通過一個氣缸控制,上下移動也是通過一個氣缸控制。而單片機控制氣缸,本質上是通過三極管把信號放大,然后控制氣缸上的電磁閥。這個系統機械手驅動部分的輸出和輸入信號如下:
        2個輸出IO口,分別控制2個氣缸。對于左右移動的氣缸,當IO口為0時往左邊跑,當IO口為1時往右邊跑。對于上下移動的氣缸,當IO口為0時往上邊跑,當IO口為1時往下邊跑。
        3個輸入IO口,分別檢測3個開關感應器。感應器沒有被觸發時,IO口檢測為高電平1。被觸發時,IO口檢測為低電平0。
        這一節繼續要教會大家兩個知識點:
        第一點:如何用軟件進行開關感應器的抗干擾處理。
        第二點:如何用Switch語句搭建工業自動控制的程序框架。還是那句話,我們只要以Switch語句為支點,再復雜再繁瑣的程序都可以輕松地編寫出來。

        具體內容,請看源代碼講解。

        (1)硬件平臺:基于朱兆祺51單片機學習板。用矩陣鍵盤中的S1鍵作為啟動獨立按鍵,用S5按鍵模擬左邊的開關感應器,用S9按鍵模擬右邊的開關感應器,用S13按鍵模擬下邊的開關感應器。記得把輸出線P0.4一直輸出低電平,模擬獨立按鍵的觸發地GND。

        (2)實現功能:
        開機默認機械手在左上方的原點位置。按下啟動按鍵后,機械手從左邊開始往右邊移動,當機械手移動到最右邊時,機械手馬上開始往下移動,最后機械手移動到最右下角的位置時,延時1秒,然后原路返回,一直返回到左上角的原點位置。注意:啟動按鍵必須等機械手處于左上角原點位置時,啟動按鍵的觸發才有效。

        (3)源代碼講解如下:
        #include "REG52.H"

        #define const_voice_short40 //蜂鳴器短叫的持續時間

        #define const_key_time120 //按鍵去抖動延時的時間


        #define const_sensor20 //開關感應器去抖動延時的時間

        #define const_1s500//1秒鐘大概的定時中斷次數

        void initial_myself();
        void initial_peripheral();
        void delay_short(unsigned int uiDelayShort);
        void delay_long(unsigned int uiDelaylong);

        void left_to_right();//從左邊移動到右邊
        void right_to_left(); //從右邊返回到左邊
        void up_to_dowm(); //從上邊移動到下邊
        void down_to_up(); //從下邊返回到上邊


        void run(); //設備自動控制程序
        void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
        void led_update();//LED更新函數
        void T0_time();//定時中斷函數

        void key_service(); //按鍵服務的應用程序
        void key_scan(); //按鍵掃描函數 放在定時中斷里
        void sensor_scan(); //開關感應器軟件抗干擾處理函數,放在定時中斷里。

        sbit hc595_sh_dr=P2^3;
        sbit hc595_st_dr=P2^4;
        sbit hc595_ds_dr=P2^5;

        sbit beep_dr=P2^7; //蜂鳴器的驅動IO口

        sbit key_sr1=P0^0; //對應朱兆祺學習板的S1鍵

        sbit left_sr=P0^1; //左邊的開關感應器 對應朱兆祺學習板的S5鍵
        sbit right_sr=P0^2; //右邊的開關感應器 有對應朱兆祺學習板的S9鍵
        sbit down_sr=P0^3; //下邊的開關感應器 對應朱兆祺學習板的S13鍵

        sbit key_gnd_dr=P0^4; //模擬獨立按鍵的地GND,因此必須一直輸出低電平

        unsigned char ucKeySec=0; //被觸發的按鍵編號

        unsigned intuiKeyTimeCnt1=0; //按鍵去抖動延時計數器
        unsigned char ucKeyLock1=0; //按鍵觸發后自鎖的變量標志


        unsigned char ucLeftSr=0;//左邊感應器經過軟件抗干擾處理后的狀態標志
        unsigned char ucRightSr=0;//右邊感應器經過軟件抗干擾處理后的狀態標志
        unsigned char ucDownSr=0;//下邊感應器經過軟件抗干擾處理后的狀態標志

        unsigned intuiLeftCnt1=0;//左邊感應器軟件抗干擾所需的計數器變量
        unsigned intuiLeftCnt2=0;

        unsigned intuiRightCnt1=0;//右邊感應器軟件抗干擾所需的計數器變量
        unsigned intuiRightCnt2=0;

        unsigned intuiDownCnt1=0; //下邊軟件抗干擾所需的計數器變量
        unsigned intuiDownCnt2=0;

        unsigned intuiVoiceCnt=0;//蜂鳴器鳴叫的持續時間計數器

        unsigned char ucLed_dr1=0; //代表16個燈的亮滅狀態,0代表滅,1代表亮
        unsigned char ucLed_dr2=0;
        unsigned char ucLed_dr3=0;
        unsigned char ucLed_dr4=0;
        unsigned char ucLed_dr5=0;
        unsigned char ucLed_dr6=0;
        unsigned char ucLed_dr7=0;
        unsigned char ucLed_dr8=0;
        unsigned char ucLed_dr9=0;
        unsigned char ucLed_dr10=0;
        unsigned char ucLed_dr11=0;
        unsigned char ucLed_dr12=0;
        unsigned char ucLed_dr13=0;
        unsigned char ucLed_dr14=0;
        unsigned char ucLed_dr15=0;
        unsigned char ucLed_dr16=0;

        unsigned char ucLed_update=1;//刷新變量。每次更改LED燈的狀態都要更新一次。



        unsigned char ucLedStatus16_09=0; //代表底層74HC595輸出狀態的中間變量
        unsigned char ucLedStatus08_01=0; //代表底層74HC595輸出狀態的中間變量



        unsigned intuiRunTimeCnt=0;//運動中的時間延時計數器變量
        unsigned char ucRunStep=0;//運動控制的步驟變量

        void main()
        {
        initial_myself();
        delay_long(100);
        initial_peripheral();
        while(1)
        {
        run(); //設備自動控制程序
        led_update();//LED更新函數
        key_service(); //按鍵服務的應用程序
        }

        }


        /* 注釋一:
        * 開關感應器的抗干擾處理,本質上類似按鍵的去抖動處理。唯一的區別是:
        * 按鍵去抖動關注的是IO口的一種狀態,而開關感應器關注的是IO口的兩種狀態。
        * 當開關感應器從原來的1狀態切換到0狀態之前,要進行軟件濾波處理過程,一旦成功地
        * 切換到0狀態了,再想從0狀態切換到1狀態的時候,又要經過軟件濾波處理過程,符合
        * 條件后才能切換到1的狀態。通俗的話來說,按鍵的去抖動從1變成0難,從0變成1容易。
        * 開關感應器從1變成0難,從0變成1也難。這里所說的"難"是指要經過去抖處理。
        */

        void sensor_scan() //開關感應器軟件抗干擾處理函數,放在定時中斷里。
        {
        if(left_sr==1)//左邊感應器是高電平,說明有可能沒有被接觸 對應朱兆祺學習板的S5鍵
        {
        uiLeftCnt1=0; //在軟件濾波中,非常關鍵的語句!!!類似按鍵去抖動程序的及時清零
        uiLeftCnt2++; //類似獨立按鍵去抖動的軟件抗干擾處理
        if(uiLeftCnt2>const_sensor)
        {
        uiLeftCnt2=0;
        ucLeftSr=1; //說明感應器確實沒有被接觸
        }
        }
        else //左邊感應器是低電平,說明有可能被接觸到了
        {
        uiLeftCnt2=0; //在軟件濾波中,非常關鍵的語句!!!類似按鍵去抖動程序的及時清零
        uiLeftCnt1++;
        if(uiLeftCnt1>const_sensor)
        {
        uiLeftCnt1=0;
        ucLeftSr=0; //說明感應器確實被接觸到了
        }
        }

        if(right_sr==1)//右邊感應器是高電平,說明有可能沒有被接觸 對應朱兆祺學習板的S9鍵
        {
        uiRightCnt1=0; //在軟件濾波中,非常關鍵的語句!!!類似按鍵去抖動程序的及時清零
        uiRightCnt2++; //類似獨立按鍵去抖動的軟件抗干擾處理
        if(uiRightCnt2>const_sensor)
        {
        uiRightCnt2=0;
        ucRightSr=1; //說明感應器確實沒有被接觸
        }
        }
        else //右邊感應器是低電平,說明有可能被接觸到了
        {
        uiRightCnt2=0; //在軟件濾波中,非常關鍵的語句!!!類似按鍵去抖動程序的及時清零
        uiRightCnt1++;
        if(uiRightCnt1>const_sensor)
        {
        uiRightCnt1=0;
        ucRightSr=0; //說明感應器確實被接觸到了
        }
        }

        if(down_sr==1)//下邊感應器是高電平,說明有可能沒有被接觸 對應朱兆祺學習板的S13鍵
        {
        uiDownCnt1=0; //在軟件濾波中,非常關鍵的語句!!!類似按鍵去抖動程序的及時清零
        uiDownCnt2++; //類似獨立按鍵去抖動的軟件抗干擾處理
        if(uiDownCnt2>const_sensor)
        {
        uiDownCnt2=0;
        ucDownSr=1; //說明感應器確實沒有被接觸
        }
        }
        else //下邊感應器是低電平,說明有可能被接觸到了
        {
        uiDownCnt2=0; //在軟件濾波中,非常關鍵的語句!!!類似按鍵去抖動程序的及時清零
        uiDownCnt1++;
        if(uiDownCnt1>const_sensor)
        {
        uiDownCnt1=0;
        ucDownSr=0; //說明感應器確實被接觸到了
        }
        }
        }


        void key_scan()//按鍵掃描函數 放在定時中斷里
        {

        if(key_sr1==1)//IO是高電平,說明按鍵沒有被按下,這時要及時清零一些標志位
        {
        ucKeyLock1=0; //按鍵自鎖標志清零
        uiKeyTimeCnt1=0;//按鍵去抖動延時計數器清零,此行非常巧妙,是我實戰中摸索出來的。
        }
        else if(ucKeyLock1==0)//有按鍵按下,且是第一次被按下
        {
        uiKeyTimeCnt1++; //累加定時中斷次數
        if(uiKeyTimeCnt1>const_key_time1)
        {
        uiKeyTimeCnt1=0;
        ucKeyLock1=1;//自鎖按鍵置位,避免一直觸發
        ucKeySec=1; //觸發1號鍵
        }
        }



        }


        void key_service() //按鍵服務的應用程序
        {
        switch(ucKeySec) //按鍵服務狀態切換
        {
        case 1:// 啟動按鍵 對應朱兆祺學習板的S1鍵
        if(ucLeftSr==0)//處于左上角原點位置
        {
        ucRunStep=1; //啟動
        uiVoiceCnt=const_voice_short; //按鍵聲音觸發,滴一聲就停。
        }

        ucKeySec=0;//響應按鍵服務處理程序后,按鍵編號清零,避免一致觸發
        break;

        }
        }



        void led_update()//LED更新函數
        {

        if(ucLed_update==1)
        {
        ucLed_update=0; //及時清零,讓它產生只更新一次的效果,避免一直更新。

        if(ucLed_dr1==1)
        {
        ucLedStatus08_01=ucLedStatus08_01|0x01;
        }
        else
        {
        ucLedStatus08_01=ucLedStatus08_01&0xfe;
        }

        if(ucLed_dr2==1)
        {
        ucLedStatus08_01=ucLedStatus08_01|0x02;
        }
        else
        {
        ucLedStatus08_01=ucLedStatus08_01&0xfd;
        }

        if(ucLed_dr3==1)
        {
        ucLedStatus08_01=ucLedStatus08_01|0x04;
        }
        else
        {
        ucLedStatus08_01=ucLedStatus08_01&0xfb;
        }

        if(ucLed_dr4==1)
        {
        ucLedStatus08_01=ucLedStatus08_01|0x08;
        }
        else
        {
        ucLedStatus08_01=ucLedStatus08_01&0xf7;
        }


        if(ucLed_dr5==1)
        {
        ucLedStatus08_01=ucLedStatus08_01|0x10;
        }
        else
        {
        ucLedStatus08_01=ucLedStatus08_01&0xef;
        }


        if(ucLed_dr6==1)
        {
        ucLedStatus08_01=ucLedStatus08_01|0x20;
        }
        else
        {
        ucLedStatus08_01=ucLedStatus08_01&0xdf;
        }


        if(ucLed_dr7==1)
        {
        ucLedStatus08_01=ucLedStatus08_01|0x40;
        }
        else
        {
        ucLedStatus08_01=ucLedStatus08_01&0xbf;
        }


        if(ucLed_dr8==1)
        {
        ucLedStatus08_01=ucLedStatus08_01|0x80;
        }
        else
        {
        ucLedStatus08_01=ucLedStatus08_01&0x7f;
        }

        if(ucLed_dr9==1)
        {
        ucLedStatus16_09=ucLedStatus16_09|0x01;
        }
        else
        {
        ucLedStatus16_09=ucLedStatus16_09&0xfe;
        }

        if(ucLed_dr10==1)
        {
        ucLedStatus16_09=ucLedStatus16_09|0x02;
        }
        else
        {
        ucLedStatus16_09=ucLedStatus16_09&0xfd;
        }

        if(ucLed_dr11==1)
        {
        ucLedStatus16_09=ucLedStatus16_09|0x04;
        }
        else
        {
        ucLedStatus16_09=ucLedStatus16_09&0xfb;
        }

        if(ucLed_dr12==1)
        {
        ucLedStatus16_09=ucLedStatus16_09|0x08;
        }
        else
        {
        ucLedStatus16_09=ucLedStatus16_09&0xf7;
        }


        if(ucLed_dr13==1)
        {
        ucLedStatus16_09=ucLedStatus16_09|0x10;
        }
        else
        {
        ucLedStatus16_09=ucLedStatus16_09&0xef;
        }


        if(ucLed_dr14==1)
        {
        ucLedStatus16_09=ucLedStatus16_09|0x20;
        }
        else
        {
        ucLedStatus16_09=ucLedStatus16_09&0xdf;
        }


        if(ucLed_dr15==1)
        {
        ucLedStatus16_09=ucLedStatus16_09|0x40;
        }
        else
        {
        ucLedStatus16_09=ucLedStatus16_09&0xbf;
        }


        if(ucLed_dr16==1)
        {
        ucLedStatus16_09=ucLedStatus16_09|0x80;
        }
        else
        {
        ucLedStatus16_09=ucLedStatus16_09&0x7f;
        }

        hc595_drive(ucLedStatus16_09,ucLedStatus08_01);//74HC595底層驅動函數

        }
        }

        void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
        {
        unsigned char i;
        unsigned char ucTempData;
        hc595_sh_dr=0;
        hc595_st_dr=0;

        ucTempData=ucLedStatusTemp16_09;//先送高8位
        for(i=0;i<8;i++)
        {
        if(ucTempData>=0x80)hc595_ds_dr=1;
        else hc595_ds_dr=0;

        hc595_sh_dr=0; //SH引腳的上升沿把數據送入寄存器
        delay_short(15);
        hc595_sh_dr=1;
        delay_short(15);

        ucTempData=ucTempData<<1;
        }

        ucTempData=ucLedStatusTemp08_01;//再先送低8位
        for(i=0;i<8;i++)
        {
        if(ucTempData>=0x80)hc595_ds_dr=1;
        else hc595_ds_dr=0;

        hc595_sh_dr=0; //SH引腳的上升沿把數據送入寄存器
        delay_short(15);
        hc595_sh_dr=1;
        delay_short(15);

        ucTempData=ucTempData<<1;
        }

        hc595_st_dr=0;//ST引腳把兩個寄存器的數據更新輸出到74HC595的輸出引腳上并且鎖存起來
        delay_short(15);
        hc595_st_dr=1;
        delay_short(15);

        hc595_sh_dr=0; //拉低,抗干擾就增強
        hc595_st_dr=0;
        hc595_ds_dr=0;

        }


        void left_to_right()//從左邊移動到右邊
        {
        ucLed_dr1=1; // 1代表左右氣缸從左邊移動到右邊

        ucLed_update=1;//刷新變量。每次更改LED燈的狀態都要更新一次。
        }
        void right_to_left() //從右邊返回到左邊
        {
        ucLed_dr1=0; // 0代表左右氣缸從右邊返回到左邊

        ucLed_update=1;//刷新變量。每次更改LED燈的狀態都要更新一次。
        }
        void up_to_down() //從上邊移動到下邊
        {
        ucLed_dr2=1; // 1代表上下氣缸從上邊移動到下邊

        ucLed_update=1;//刷新變量。每次更改LED燈的狀態都要更新一次。
        }
        void down_to_up() //從下邊返回到上邊
        {
        ucLed_dr2=0; // 0代表上下氣缸從下邊返回到上邊

        ucLed_update=1;//刷新變量。每次更改LED燈的狀態都要更新一次。
        }


        void run() //設備自動控制程序
        {

        switch(ucRunStep)
        {
        case 0: //機械手處于左上角原點的位置,待命狀態。此時觸發啟動按鍵ucRunStep=1,就觸發后續一些列的連續動作。

        break;

        case 1: //機械手從左邊往右邊移動
        left_to_right();
        ucRunStep=2;//這就是鴻哥傳說中的怎樣靈活控制步驟變量
        break;

        case 2: //等待機械手移動到最右邊,直到觸發了最右邊的開關感應器。
        if(ucRightSr==0)//右邊感應器被觸發
        {
        ucRunStep=3;//這就是鴻哥傳說中的怎樣靈活控制步驟變量
        }
        break;

        case 3: //機械手從右上邊往右下邊移動,從上往下。
        up_to_down();
        ucRunStep=4;//這就是鴻哥傳說中的怎樣靈活控制步驟變量
        break;

        case 4: //等待機械手從右上邊移動到右下邊,直到觸發了右下邊的開關感應器。
        if(ucDownSr==0)//右下邊感應器被觸發
        {
        uiRunTimeCnt=0;//時間計數器清零,為接下來延時1秒鐘做準備
        ucRunStep=5;//這就是鴻哥傳說中的怎樣靈活控制步驟變量
        }
        break;

        case 5: //機械手在右下邊延時1秒
        if(uiRunTimeCnt>const_1s)//延時1秒
        {
        ucRunStep=6;//這就是鴻哥傳說中的怎樣靈活控制步驟變量
        }
        break;
        case 6: //原路返回,機械手從右下邊往右上邊移動。
        down_to_up();
        ucRunStep=7;//這就是鴻哥傳說中的怎樣靈活控制步驟變量
        break;

        case 7: //原路返回,等待機械手移動到最右邊的感應開關
        if(ucRightSr==0)
        {
        ucRunStep=8;//這就是鴻哥傳說中的怎樣靈活控制步驟變量
        }
        break;

        case 8: //原路返回,等待機械手從右邊往左邊移動
        right_to_left();
        ucRunStep=9;//這就是鴻哥傳說中的怎樣靈活控制步驟變量

        break;

        case 9: //原路返回,等待機械手移動到最左邊的感應開關,表示返回到了原點
        if(ucLeftSr==0) //返回到左上角的原點位置
        {
        ucRunStep=0;//這就是鴻哥傳說中的怎樣靈活控制步驟變量
        }
        break;
        }
        }


        void T0_time() interrupt 1
        {
        TF0=0;//清除中斷標志
        TR0=0; //關中斷


        sensor_scan(); //開關感應器軟件抗干擾處理函數
        key_scan(); //按鍵掃描函數

        if(uiRunTimeCnt<0xffff) //不要超過最大int類型范圍
        {
        uiRunTimeCnt++; //延時計數器
        }
        if(uiVoiceCnt!=0)
        {
        uiVoiceCnt--; //每次進入定時中斷都自減1,直到等于零為止。才停止鳴叫
        beep_dr=0;//蜂鳴器是PNP三極管控制,低電平就開始鳴叫。
        }
        else
        {
        ; //此處多加一個空指令,想維持跟if括號語句的數量對稱,都是兩條指令。不加也可以。
        beep_dr=1;//蜂鳴器是PNP三極管控制,高電平就停止鳴叫。
        }

        TH0=0xf8; //重裝初始值(65535-2000)=63535=0xf82f
        TL0=0x2f;
        TR0=1;//開中斷
        }

        void delay_short(unsigned int uiDelayShort)
        {
        unsigned int i;
        for(i=0;i {
        ; //一個分號相當于執行一條空語句
        }
        }

        void delay_long(unsigned int uiDelayLong)
        {
        unsigned int i;
        unsigned int j;
        for(i=0;i {
        for(j=0;j<500;j++)//內嵌循環的空指令數量
        {
        ; //一個分號相當于執行一條空語句
        }
        }
        }


        void initial_myself()//第一區 初始化單片機
        {
        /* 注釋二:
        * 矩陣鍵盤也可以做獨立按鍵,前提是把某一根公共輸出線輸出低電平,
        * 模擬獨立按鍵的觸發地,本程序中,把key_gnd_dr輸出低電平。
        * 朱兆祺51學習板的S1就是本程序中用到的一個獨立按鍵。
        */
        key_gnd_dr=0; //模擬獨立按鍵的地GND,因此必須一直輸出低電平

        beep_dr=1; //用PNP三極管控制蜂鳴器,輸出高電平時不叫。

        TMOD=0x01;//設置定時器0為工作方式1


        TH0=0xf8; //重裝初始值(65535-2000)=63535=0xf82f
        TL0=0x2f;


        }

        void initial_peripheral() //第二區 初始化外圍
        {
        EA=1; //開總中斷
        ET0=1; //允許定時中斷
        TR0=1; //啟動定時中斷

        }

        總結陳詞:
        前面花了很多節內容在講按鍵和跑馬燈的關系,但是一直沒涉及到人機界面,在大多數的實際項目中,人機界面是必不可少的。人機界面的程序框架該怎么樣寫?欲知詳情,請聽下回分解-----在主函數while循環中驅動數碼管的動態掃描程序。


        評論


        技術專區

        關閉
        主站蜘蛛池模板: 阿坝| 印江| 乐山市| 尉犁县| 连南| 修武县| 桦甸市| 皋兰县| 台前县| 江川县| 沧源| 信丰县| 龙州县| 富民县| 施甸县| 陇南市| 元谋县| 津南区| 昌吉市| 密山市| 奈曼旗| 平凉市| 陆河县| 宁阳县| 龙江县| 昭苏县| 大悟县| 屯留县| 乌鲁木齐市| 措美县| 高淳县| 息烽县| 婺源县| 金川县| 会昌县| 叙永县| 阜平县| 寿宁县| 广宁县| 高要市| 华阴市|