新聞中心

        EEPW首頁 > 嵌入式系統 > 設計應用 > 建立一個屬于自己的AVR的RTOS

        建立一個屬于自己的AVR的RTOS

        作者: 時間:2011-04-29 來源:網絡 收藏

        自從03年以來,對單片機的的學習和應用的熱潮可謂一浪高過一浪.03年,在離開校園前的,非典的那幾個月,在華師的后門那里買了本邵貝貝的《UCOSII》,通讀了幾次,沒有實驗器材,也不了了之。

        在21IC上,大家都可以看到楊屹寫的關于UCOSII在51上的移植,于是掀起了51上的的熱潮。

        再后來,陳明計先生推出的smallrots,展示了用在51上的微內核,足以在52上進行任務調度。

        前段時間,在ouravr上面開有專門關于的Rtos的專欄,并且不少的兄弟把的作品拿出來,著實開了不少眼界。這時,我重新回顧了使用單片機的經歷,覺得很有必要,從根本上對單片機的的知識進行整理,于是,我開始了編寫用在單片機的RTOS。

        當時,我所有的知識和資源有:

        Proteus6.7可以用來模擬仿真avr系列的單片機
        Winv2.0.5.48基于GCCAVR的編譯環境,好處在于可以在C語言中插入asm的語句
        mega81K的ram有8K的rom,是開發8位的RTOS的理想的器件,并且我對它也比較熟悉。

        寫UCOS的JeanJ.Labrosse在他的書上有這樣一句話,“漸漸地,我自然會想到,寫個實時內核直有那么難嗎?不就是不斷地保存,恢復CPU的那些寄存器嘛。”

        好了,當這一切準備好后,我們就可以開始我們的Rtosformega8的實驗之旅了。

        本文列出的例子,全部完整可用。只需要一個文件就可以編譯了。我相信,只要適當可用,最簡單的就是最好的,這樣可以排除一些不必要的干擾,讓大家專注到每一個過程的學習。

        第一篇:函數的運行

        在一般的單片機系統中,是以前后臺的方式(大循環+中斷)來處理數據和作出反應的。
        例子如下:

        makefile的設定:運行WinAvr中的Mfile,設定如下
        MCUType:mega8
        Optimizationlevel:s
        Debugformat:AVR-COFF
        C/C++sourcefile:選譯要編譯的C文件

        #includeavr/io.h>
        voidfun1(void)
        {
        unsignedchari=0;
        while(1)
        {
        PORTB=i++;
        PORTC=0x01(i%8);
        }
        }

        intmain(void)
        {
        fun1();
        }

        首先,提出一個問題:如果要調用一個函數,真是只能以上面的方式進行嗎?
        相信學習過C語言的各位會回答,No!我們還有一種方式,就是“用函數指針變量調用函數”,如果大家都和我一樣,當初的教科書是譚浩強先生的《C程序設計》的話,請找回書的第9.5節。

        例子:用函數指針變量調用函數


        #includeavr/io.h>
        voidfun1(void)
        {
        unsignedchari=0;
        while(1)
        {
        PORTB=i++;
        PORTC=0x01(i%8);
        }
        }
        void(*pfun)();//指向函數的指針

        intmain(void)
        {

        pfun=fun1;//
        (*pfun)();//運行指針所指向的函數
        }

        第二種,是“把指向函數的指針變量作函數參數”

        #includeavr/io.h>
        voidfun1(void)
        {
        unsignedchari=0;
        while(1)
        {
        PORTB=i++;
        PORTC=0x01(i%8);
        }
        }

        voidRunFun(void(*pfun)())//獲得了要傳遞的函數的地址
        {
        (*pfun)();//在RunFun中,運行指針所指向的函數
        }

        intmain(void)
        {
        RunFun(fun1);//將函數的指針作為變量傳遞

        }

        看到上面的兩種方式,很多人可能會說,“這的確不錯”,但是這樣與我們想要的RTOS,有什么關系呢?各位請細心向下看。

        以下是GCC對上面的代碼的編譯的情況:

        對main()中的RunFun(fun1);的編譯如下
        ldir24,lo8(pm(fun1))
        ldir25,hi8(pm(fun1))
        rcallRunFun

        對voidRunFun(void(*pfun)())的編譯如下
        /*voidRunFun(void(*pfun)())*/
        /*(*pfun)();*/
        .LM6:
        movwr30,r24
        icall
        ret

        在調用voidRunFun(void(*pfun)())的時候,的確可以把fun1的地址通過r24和r25傳遞給RunFun()。但是,RTOS如何才能有效地利用函數的地址呢?

        本文引用地址:http://www.104case.com/article/172857.htm

        第二篇:人工堆棧

        在單片機的指令集中,一類指令是專門與堆棧和PC指針打道的,它們是
        rcall相對調用子程序指令
        icall間接調用子程序指令
        ret子程序返回指令
        reti中斷返回指令

        對于ret和reti,它們都可以將堆棧棧頂的兩個字節被彈出來送入程序計數器PC中,一般用來從子程序或中斷中退出。其中reti還可以在退出中斷時,重開全局中斷使能。
        有了這個基礎,就可以我們的人工堆棧了。
        例:
        #includeavr/io.h>
        voidfun1(void)
        {
        unsignedchari=0;
        while(1)
        {
        PORTB=i++;
        PORTC=0x01(i%8);
        }
        }

        unsignedcharStack[100];//一個100字節的人工堆棧

        voidRunFunInNewStack(void(*pfun)(),unsignedchar*pStack)
        {
        *pStack--=(unsignedint)pfun>>8;//將函數的地址高位壓入堆棧,
        *pStack--=(unsignedint)pfun;//將函數的地址低位壓入堆棧,
        SP=pStack;//將堆棧指針指向人工堆棧的棧頂
        __asm____volatile__(RETnt);//返回并開中斷,開始運行fun1()

        }

        intmain(void)
        {
        RunFunInNewStack(fun1,Stack[99]);
        }
        RunFunInNewStack(),將指向函數的指針的值保存到一個unsignedchar的數組Stack中,作為人工堆棧。并且將棧頂的數值傳遞組堆棧指針SP,因此當用ret返回時,從SP中恢復到PC中的值,就變為了指向fun1()的地址,開始運行fun1().

        上面例子中在RunFunInNewStack()的最后一句嵌入了匯編代碼ret,實際上是可以去除的。因為在RunFunInNewStack()返回時,編譯器已經會加上ret。我特意寫出來,是為了讓大家看到用ret作為返回后運行fun1()的過程。

        第三篇:GCC中對寄存器的分配與使用

        在很多用于AVR的RTOS中,都會有任務調度時,插入以下的語句:

        入棧:
        __asm____volatile__(PUSHR0nt);
        __asm____volatile__(PUSHR1nt);
        ......
        __asm____volatile__(PUSHR31nt);

        出棧
        __asm____volatile__(POPR31nt);
        ......
        __asm____volatile__(POPR1nt);
        __asm____volatile__(POPR0nt);

        通常大家都會認為,在任務調度開始時,當然要將所有的通用寄存器都保存,并且還應該保存程序狀態寄存器SREG。然后再根據相反的次序,將新任務的寄存器的內容恢復。

        但是,事實真的是這樣嗎?如果大家看過陳明計先生寫的smallrots51,就會發現,它所保存的通用寄存器不過是4組通用寄存器中的1組。

        在WinAVR中的幫助文件avr-libcManual中的RelatedPages中的FrequentlyAskedQuestions,其實有一個問題是WhatregistersareusedbytheCcompiler?回答了編譯器所需要占用的寄存器。一般情況下,編譯器會先用到以下寄存器
        1Call-usedregisters(r18-r27,r30-r31):調用函數時作為參數傳遞,也就是用得最多的寄存器。

        2Call-savedregisters(r2-r17,r28-r29):調用函數時作為結果傳遞,當中的r28和r29可能會被作為指向堆棧上的變量的指針。

        3Fixedregisters(r0,r1):固定作用。r0用于存放臨時數據,r1用于存放0。


        還有另一個問題是Howtopermanentlybindavariabletoaregister?,是將變量綁定到通用寄存器的方法。而且我發現,如果將某個寄存器定義為變量,編譯器就會不將該寄存器分配作其它用途。這對RTOS是很重要的。

        在InlineAsm中的CNamesUsedinAssemblerCode明確表示,如果將太多的通用寄存器定義為變量,剛在編譯的過程中,被定義的變量依然可能被編譯器占用。

        大家可以比較以下兩個例子,看看編譯器產生的代碼:(在*.lst文件中)

        第一個例子:沒有定義通用寄存器為變量

        #includeavr/io.h>

        unsignedcharadd(unsignedcharb,unsignedcharc,unsignedchard)
        {
        returnb+c*d;
        }

        intmain(void)
        {
        unsignedchara=0;
        while(1)
        {
        a++;
        PORTB=add(a,a,a);
        }
        }

        在本例中,add(a,a,a);被編譯如下:
        movr20,r28
        movr22,r28
        movr24,r28
        rcalladd

        第二個例子:定義通用寄存器為變量

        #includeavr/io.h>

        unsignedcharadd(unsignedcharb,unsignedcharc,unsignedchard)
        {
        returnb+c*d;
        }


        registerunsignedcharaasm(r20);//將r20定義為變量a

        intmain(void)
        {

        while(1)
        {
        a++;
        PORTB=add(a,a,a);
        }
        }

        在本例中,add(a,a,a);被編譯如下:
        movr22,r20
        movr24,r20
        rcalladd

        當然,在上面兩個例子中,有部份代碼被編譯器優化了。

        通過反復測試,發現編譯器一般使用如下寄存器:
        第1類寄存器,第2類寄存器的r28,r29,第3類寄存器

        如在中斷函數中有調用基它函數,剛會在進入中斷后,固定地將第1類寄存器和第3類寄存器入棧,在退出中斷又將它們出棧。

        第四篇:只有延時服務的協作式的內核

        CooperativeMultitasking

        前后臺系統,協作式內核系統,與占先式內核系統,有什么不同呢?

        記得在21IC上看過這樣的比喻,“你(小工)在用廁所,經理在外面排第一,老板在外面排第二。如果是前后臺,不管是誰,都必須按排隊的次序使用廁所;如果是協作式,那么可以等你用完廁所,老板就要比經理先進入;如果是占先式,只要有更高級的人在外面等,那么廁所里無論是誰,都要第一時間讓出來,讓最高級別的人先用。”


        #includeavr/io.h>
        #includeavr/Interrupt.h>
        #includeavr/signal.h>
        unsignedcharStack[200];

        registerunsignedcharOSRdyTblasm(r2);//任務運行就緒表
        registerunsignedcharOSTaskRunningPrioasm(r3);//正在運行的任務

        #defineOS_TASKS3//設定運行任務的數量
        structTaskCtrBlock//任務控制塊
        {
        unsignedintOSTaskStackTop;//保存任務的堆棧頂


        上一頁 1 2 3 4 5 6 下一頁

        評論


        相關推薦

        技術專區

        關閉
        主站蜘蛛池模板: 大关县| 新乐市| 新野县| 厦门市| 扬中市| 白沙| 广昌县| 丽江市| 县级市| 蒙城县| 罗源县| 德化县| 汪清县| 清苑县| 海原县| 高雄市| 裕民县| 楚雄市| 岳池县| 海门市| 西和县| 西充县| 漳州市| 肃宁县| 潮安县| 永嘉县| 建水县| 饶河县| 黑水县| 通榆县| 永州市| 霍城县| 哈尔滨市| 水富县| 达尔| 景谷| 林西县| 祁门县| 湖州市| 赤壁市| 信阳市|