娇小w搡bbbb搡bbb,《第一次の人妻》,中国成熟妇女毛茸茸,边啃奶头边躁狠狠躁视频免费观看

歷史上的今天

今天是:2024年09月08日(星期日)

2020年09月08日 | AVR單片機教程——走向高層

發布者:Aq123456258 來源: eefocus關鍵字:AVR  單片機  ADC 手機看文章 掃描二維碼
隨時隨地手機看文章

在系列教程的最后一篇中,我將向你推薦3個可以深造的方向:C++、事件驅動、RTOS。掌握這些技術可以幫助你更快、更好地開發更大的項目。


本文涉及到許多概念性的內容,如果你有不同意見,歡迎討論。


關于高層

這一篇教程叫作“走向高層”。什么是高層?


我認為,如果寥寥幾行代碼就能實現一個復雜功能,或者一行代碼可以對應到幾百句匯編,那么你就站在高層。高層與底層是相對的概念,沒有絕對的界限。


站得高,看得遠,這同樣適用于編程,我們要走向高層。高層是對底層的封裝,是對現實的抽象,高層相比于底層更加貼近應用。站在高層,你可以看到很多底層看不到的東西,主要有編程工具和思路。合理利用工具,可以簡化代碼,降低工作量;用合適的思路編程,更可以事半功倍。


但是,掌握高層并不意味著忽視甚至鄙視底層,高層建立在底層基礎之上。其一,有些高層出現的詭異現象可以追溯到底層,這樣的debug任務只有通曉底層與高層的開發者才能勝任;其二,為了讓高層實現復雜功能的同時獲得可接受的運行效率,底層必須設計地更加精致,這就對底層提出了更高的要求。


相信你經過一期和二期的教程,已經相當熟悉AVR編程的底層了。跟我一起走上高層吧!


C++

C++繼承自C,兼有低級語言和高級語言的特性。C++代碼可以被編譯為匯編語句,這決定了它的高效;C++支持過程式、面向對象、泛型等編程范式,還有龐大的標準庫,這決定了它的高級。所有C++代碼都可以轉換成C代碼,使用C++絕非必要,但是不能僅憑這一點而否定C++——因為同理,C也是沒有必要的,你為什么還要學C呢?我們使用C++,就是要發揮它的高級。


在幾十年的發展過程中,C++委員會制定了多個標準,主要的有C++98和C++11,C++11添加了很多新特性,既能簡化程序又能提升性能。本文寫于下一個主要標準C++20即將發布之際,主流編譯器對C++11的支持已經很完整,所以我的建議是,如果想學C++,按照C++11標準來學。


在AVR平臺上寫C++比較特殊,在于工具鏈沒有提供C++標準庫。如果你想用標準庫,無論是IO設施還是容器算法,要么委曲求全,用網上能找到的不完整的實現,要么自己寫。這是AVR很勸退C++的一點,對此我的建議是,不要把AVR作為你學習C++的平臺,盡管這一節講的是C++給AVR單片機開發帶來的益處。用AVR來操練C++倒是有很多意想不到的好處。


說起AVR與C++,還不得不提起Arduino,這個平臺從AVR起家,一直使用C++語言,是AVR平臺上C++的最大甚至唯一的應用。我想,如果你能讀到這里,你對Arduino肯定不會陌生,方便易用是它的核心賣點之一。事實上,C++這門語言為它提供了不少幫助,而C++的威力還不止于此。我將以范式為線索,介紹C++的高級之處。


在面向對象的世界中,對象和消息是主角,語句是創建對象和規定消息傳遞方式的工具。定義對象需要用類,定義消息需要類中的函數,安排類之間的關系需要繼承和虛函數,這些是功能上的配角,編程邏輯上的主角。


代碼中的對象是現實中的對象的抽象,由于單片機往往是跟現實世界打交道,這一點比較容易理解。比如,開發板上的每一個外設,包括LED、按鍵等,都是對象。


#include

#define F_CPU 25000000

#include


class Led

{

public:

    Led(uint8_t index)

    {

        mask = 1 << (4 + index);

        DDRC |= mask;

    }

    void on()

    {

        PORTC |= mask;

    }

    void off()

    {

        PORTC &= ~mask;

    }

private:

    uint8_t mask;

};


Led red(0), yellow(1), green(2), blue(3);


int main()

{

    while (1)

    {

        red.on();

        blue.off();

        _delay_ms(500);

        red.off();

        blue.on();

        _delay_ms(500);

    }

}

在這個程序中,我定義了類Led,它有3個函數:構造函數Led(uint8_t),設置mask并在硬件上初始化LED;on和off,分別開和關LED。然后創建了4個全局變量,分別代表4個LED。最后在main中使用,red.on()使紅燈亮,是不是很形象呢?除了形象,你也許還注意到,我沒有顯式調用含有DDRC的那個函數,實際上它在main之前創建全局變量的時候被調用,自動地初始化硬件。我打賭你之前一定忘記過初始化,而C++幫你解決了這個問題。對象的構造、復制、移動、銷毀,以及內存分配,都可以交給C++的語法和標準庫來處理。


上面這個程序實際上是基于對象范式的,它體現了抽象和封裝。面向對象則更進一步,體現了繼承和多態。繼承是類與類之間的關系,如果類D繼承自類B,那么D類型的對象就包含B類型對象所包含的一切內容,類B稱為類D的基類,類D稱為類B的派生類。多態是指相同的語句表現出不同的行為,換言之不同行為可以有統一的接口,可分為編譯期多態和運行時多態。運行時多態體現為虛函數:基類定義虛函數,派生類們實現虛函數,通過基類調用時,實際屬于不同派生類的對象表現出不同的行為。很難理解吧,我們通過實例來看:


#include


class Shape

{

public:

    virtual void draw() const = 0;

};


class Line : public Shape

{

public:

    Line(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2)

        : x1(x1), y1(y1), x2(x2), y2(y2) { }

    virtual void draw() const override

    {

        // Bresenham line...

    }

private:

    uint8_t x1, y1, x2, y2;

};


class Circle : public Shape

{

public:

    Circle(uint8_t x, uint8_t y, uint8_t r)

        : x(x), y(y), r(r) { }

    virtual void draw() const override

    {

        // Bresenham circle...

    }

private:

    uint8_t x, y, r;

};


int main(void)

{

    Line line(0, 0, 127, 63);

    Circle circle(64, 32, 16);

    Shape* shapes[2];

    shapes[0] = &line;

    shapes[1] = &circle;

    for (uint8_t i = 0; i != sizeof(shapes) / sizeof(*shapes); ++i)

        shapes[i]->draw();

    while (1)

        ;

}

override是C++11引入的關鍵字,你需要在項目屬性->Toolchain->AVR/GNU C++ Compiler->Miscellaneous->Other flags中寫-std=c++11,以啟用C++11標準。


這個程序涉及3個類:

基類Shape定義了虛函數draw,派生類Line和Circle提供了不同的實現。main創建了Line和Circle類各一個對象,把指針放到Shape*類型的數組shapes中,然后通過指針調用draw函數,結果是Line::draw和Circle::draw分別被調用一次。要注意,只有通過指針或引用調用虛函數才能多態。引用用類型后的&表示,功能與指針類似,但不用寫取地址和解引用符號,形式上更加簡潔一點。


正如你所見,面向對象范式可以幫助你構建起現實中的對象之間的關系。在大型程序中,類與類、對象與對象、類與對象之間有復雜的聯系,形成了各種設計模式。


C++和C一樣可以自定義數據結構,但是不像C一樣用宏來定義ADT,也不用void*來抹去類型,而是引入了模板,可以把元素類型作為模板參數定義類和函數,一個模板類或模板函數可以實例化成很多個類或函數。比如我們在UART一講中寫過的隊列,在C++中可以這么寫:


#include


template

class Queue

{

public:

    Queue() = default;

    Queue(const Queue&) = delete;

    Queue& operator=(const Queue&) = delete;

    Queue(Queue&&) = delete;

    Queue& operator=(Queue&&) = delete;

    void push(const T& element)

    {

        data[tail] = element;

        tail = increase(tail);

    }

    void pop()

    {

        head = increase(head);

    }

    bool empty() const

    {

        return head == tail;

    }

    bool full() const

    {

        return increase(tail) == head;

    }

    const T& peek() const

    {

        return data[head];

    }

private:

    T data[S];

    size_t head = 0;

    size_t tail = 0;

    static size_t increase(size_t value)

    {

        if (++value == S)

            value = 0;

        return value;

    }

};

順便把UART那些函數改寫一下:


#include

#include

#include

#include

#include

#define F_CPU 25000000

#include


class Uart

{

public:

    Uart()

    {

        UCSR0B =    0 << UDRIE0  // UDRE interrupt disabled

               |    1 << TXEN0;  // TX only

        UCSR0C = 0b00 << UMSEL00 // asynchronous USART

               | 0b10 << UPM00   // even parity

               |    0 << USBS0   // 1 stop bit

               | 0b11 << UCSZ00; // 8-bit

        UBRR0L = 40;             // 38400bps

    }

    void print(char c)

    {

        bool full = true;

        while (1)

        {

            ATOMIC_BLOCK(ATOMIC_FORCEON)

            {

                if (!queue.full())

                    full = false;

            }

            if (!full)

                break; // if full, wait until buffer is not full

        }

        ATOMIC_BLOCK(ATOMIC_FORCEON)

        {

            if (queue.empty())

                UCSR0B |= 1 << UDRIE0;

            queue.push(c);

        }

    }

    void print(int i)

    {

        char str[10];

        itoa(i, str, 10);

        print(str);

    }

    void print(const char* s)

    {

        for (; *s; ++s)

            print(*s);

    }

    void println()

    {

        print('n');

    }

    template

    void println(const T& t)

    {

        print(t);

        println();

    }

    void _interrupt()

    {

        UDR0 = queue.peek();

        queue.pop();

        if (queue.empty())

            UCSR0B &= ~(1 << UDRIE0);

    }

private:

    Queue queue;

} uart;


ISR(USART0_UDRE_vect)

{

    uart._interrupt();

}


int main()

{

    for (char c = ' '; c <= '~'; ++c)

        uart.print(c);

    uart.println();

    for (int16_t i = 0; ; ++i)

    {

        uart.println(i);

        _delay_ms(500);

    }

}

有什么好處呢?第一段代碼把原來為char編寫的64字節隊列緩沖區改寫成任意類型、任意長度的隊列類模板Queue,以類型T與大小S為模板參數。Uart類中創建了Queue queue,實現原來的功能,而Queue類模板還可以在其他地方使用,比如處理用戶輸入的指令:Queue instr;。


第二段代碼是把C代碼轉換成了基于對象風格的C++代碼,這個剛剛介紹過了,亮點在于函數print和println。print這一個函數名字對應參數為char、int和const char*的三個版本,這三個函數是重載函數。如果你給print函數傳一個參數,編譯器會幫你匹配到最合適的一個重載。在沒有模板的情況下,你可以把重載看作是一個人性化的功能,畢竟你是知道實際上哪個函數會被調用的,如果你給函數名字加個表示參數類型的后綴,你也可以不使用重載,只不過這件事現在可以交給編譯器了。


講到函數重載,不得不順便提一下運算符重載。私以為,運算符重載是C++最美麗的特性之一。在Java中你可以把兩個字符串用+號連接,C++也可以;C++還允許你賦予運算符以意義,即為原本語義上不成立的運算符編寫代碼,它們往往與形象或數學上的意義相契合,比如用operator<<輸出,再比如寫有理數Rational類然后重載四則運算和比較運算符,而Java中只能覆寫Object類的equals方法。況且直觀上,operator==是比equals更加原生的寫法——==是運算符,屬于語言核心,而equals是函數名字,屬于標準庫。總之,函數重載省去了你為相同功能的函數起不同名字的麻煩,運算符重載則更加直接,連函數名字都省了。


回到正題,在有模板的情況下,重載不再僅僅是人性化功能,而是與泛型緊密結合了。println是一個函數模板,盡管你知道它只能接受char、int和const char*類型的參數,但是寫成模板一可以省事,二允許添加額外的print重載而不改變println。如果給print加上后綴,你能在println中知道要調用哪個嗎?這也是一種多態,編譯期多態。


不過,如果你給println傳了一個錯誤的類型,編譯器給的錯誤信息會很難看,這是C++中模板錯誤的通病。如果不跳票的話,C++20引入的concept會解決這個問題。


Uart還可以再優化一下,讓它繼承自基類Print,其中定義了virtual print(char),其他函數照抄,Uart中只需覆寫這一個print(char)函數,其他print和println都可以刪去,而客戶依然可以使用它們。這是因為,雖然println定義在Print中,但println中調用的print(char)是虛函數,還是會回到Uart::print(char)上來。同樣地還可以有Oled類繼承自Print類,同樣覆寫自己的print(char)。如果一個函數debug需要打印,它應該接受Print&類型的參數,傳入Uart和Oled的實例可以分別實現在串口和OLED屏上打印。

[1] [2] [3] [4]
關鍵字:AVR  單片機  ADC 引用地址:AVR單片機教程——走向高層

上一篇:ATmega328P定時器詳解
下一篇:AVR單片機教程——示波器

推薦閱讀

使用STVD建立完匯編工程項目之后(具本建立方法可以看我的另一篇博文http://blog.csdn.net/u010093140/article/details/49983397),可以看到這個目錄結構(以STM8S105C6芯片為例) 其中.asm文件是匯編代碼的源文件,.inc文件是包含文件,類似于C語言當在的.c文件和.h文件。接下來讓我們來分析一下這三個文件。(分析匯編代碼最好也要對STM8單片機的啟動...
近日有開發者爆料稱蘋果官方在給自己打上個月App收入時似乎誤將本應以人民幣為結算單位的錢當成美元打給開發者了,這也導致其收入因匯率的原因直接翻了7倍,而這筆外匯目前已經到賬且已可申報。此后,蘋果給開發者發郵件稱是德意志銀行出錯了。蘋果“請求”開發者們批準退匯請求,另外會再匯一筆正確的金額。有趣的是爆料蘋果誤用美元結算工資事件的開發者...
近年來,伴隨著無人機技術和產業的不斷發展,無人機應用也是越來越多樣化。9月2日,據有關媒體報道,我國長江三峽庫區便利用無人機順利完成了航道巡檢工作,不僅有效解決了汛期所帶來的航道漂浮物堆積等問題,同時也保障了三峽水利設施的穩定正常運行。由此可見,除了主流的農業、工業、娛樂等應用之外,無人機在水利方面也具有廣泛用武之地。水利智慧化趨...
.h文件如下:#ifndef __IWDG_H#define __IWDG_H #include "stm8s.h" void IWDG_Init(void);void IWDG_Feed(void);#endif.c文件如下:#include "iwdg.h" void IWDG_Init(void) //配置并啟動看門狗 //獨立看門狗,時間1.02S{ IWDG->KR = 0xcc; //啟動獨立看門狗 IWDG->KR = 0x55; //寫入解鎖 IWDG->PR = 0x06;...

史海拾趣

問答坊 | AI 解惑

接電話模塊ph8810

void checkRing_inti() {         check0=2;         count0=20; } uchar check_ring() {         uchar flag;         flag=ring;     &nbs ...…

查看全部問答∨

如何打開視頻文件

我現在用的是VS2003里面的“智能設備應用程序”來開發移動設備上的AP(其中,我編程語言用的是C#),可是我現在想打開視頻文件,不知道應該怎么做。 我也有查過網上,有人說“打開的自定義工具箱的窗口,在“COM  組件”中選擇Window  ...…

查看全部問答∨

對STM32在RVMDK大家用不用微庫microlib???

                                 選擇微庫編譯后并沒有比標準庫小啊。大家一般用不用微庫?…

查看全部問答∨

LM3S6911+TFTP程序升級

我最近做了個LM3S6911帶LWIP的SNMP網絡通訊的程序,程序做好后想從網絡口燒寫程序,希望大家提供方法和代碼!…

查看全部問答∨

三極管放大區Vce怎么決定

這是一個穩壓電路,最終輸出1.7V(R7上端為+),R6為電源輸入端。 二極管LM285-1穩壓1.235V. 我理解輸出電壓通過R7,R8分壓傳至比較器,來控制V3,形成負反饋, 但不知道V3是工作在放大狀態,還是工作在開關狀態, 發射機正徧,集電極反偏,應該是 ...…

查看全部問答∨

求zigbee全套資料,想入門

課題做采集水流量傳感器的信號,通過zigbee傳送給上位機。求大神指導…

查看全部問答∨

stepping stone能否自動識別壞塊

S3C2440會自動加載Nand前4K內容,倘若Nand的第1塊被標記為壞塊,是不是不會自動跳轉加載第2塊,因為我找到S3C2440只有NCON0、GPG13、GPG14、GPG15可以告知Page大小,而沒有告知Block大小的引腳。 所以我猜測S3C2440啟動前不進行壞塊檢測?。?! 另 ...…

查看全部問答∨

【R7F0C809】實現定時器的外部計數功能

   昨天使用定時器做了一個PWM功能,現在用定時器做個計數功能,就是計數方波的個數。這個功能在叫外部事件計數器。就是對外部的方波進行計數。一下就是這個功能的一些說明:    計數器可以用16位或者8位定時器進行計數,實際 ...…

查看全部問答∨

求幫助:為什么使用XDS200無法對F2812燒寫程序呢?

我是通過XDS200燒寫器使用Uniflash3.4對F2812進行燒寫完全沒問題。但是換成CCS6就不行了,在進入debug模式的時候會跳出這樣一個對話框: 無視這個提示之后,照樣可以Connect接上,但是出現讀入程序失敗的提示。 …

查看全部問答∨

Meeting notice2016國際光電子與微電子技術及應用研討會

會議通知 Meeting notice2016國際光電子與微電子技術及應用研討會 International Conference on Optoelectronics and Microelectronics Technology and Application 時間:2016年10月10-12日地點:上海光大會展中心        O ...…

查看全部問答∨
小廣播
設計資源 培訓 開發板 精華推薦

最新單片機文章
何立民專欄 單片機及嵌入式寶典

北京航空航天大學教授,20余年來致力于單片機與嵌入式系統推廣工作。

 
EEWorld訂閱號

 
EEWorld服務號

 
汽車開發圈

 
機器人開發圈

電子工程世界版權所有 京ICP證060456號 京ICP備10001474號-1 電信業務審批[2006]字第258號函 京公網安備 11010802033920號 Copyright ? 2005-2025 EEWORLD.com.cn, Inc. All rights reserved
主站蜘蛛池模板: 湖南省| 宁远县| 镇原县| 连南| 舟曲县| 乌兰浩特市| 绵阳市| 乡城县| 栾川县| 桦甸市| 大港区| 青川县| 喜德县| 平凉市| 辽源市| 奎屯市| 田东县| 绥棱县| 托里县| 汾西县| 冷水江市| 云龙县| 白银市| 巫山县| 尉氏县| 贵德县| 榕江县| 昂仁县| 措勤县| 瑞金市| 根河市| 彰化市| 射阳县| 安仁县| 扶风县| 云阳县| 涪陵区| 平原县| 肥乡县| 抚远县| 昭觉县|