主頁 > 後端開發 > AVR單片機教程——數字IO暫存器

AVR單片機教程——數字IO暫存器

2020-09-18 06:49:48 後端開發

前兩篇教程中我們學習了LED、按鍵、開關的基本原理,數字輸入輸出的使用以及兩者之間的關系,我們用到了 pin_mode 、 pin_read 和 pin_write 這三個函式,實際上它們離最底層(至少是單片機制造商允許我們接觸到的最底層)就只有一步之遙了,而學單片機要是不了解一點底層,那跟Arduino玩家還有什么區別?(為防止有忠實的Arduino粉絲罵我,我得承認還是有一小部分Arduino玩家是知道本篇教程所介紹內容的,)根本不好意思說自己學過單片機好吧,這所謂的最底層,就是數字IO暫存器了,

在開始之前,你需要下載兩份檔案:

單片機的資料手冊,官網鏈接極慢,我在國內平臺上傳了一份,在本篇教程寫成之時是最新的,

開發板的原理圖,本應在教程之初就放出來,但事實證明沒有原理圖也不影響使用,現在是肯定需要的,

 

等等,你可能還不知道暫存器是什么,那我們就從暫存器開始吧,

暫存器是一類CPU內部的存盤器,分為通用暫存器與特殊功能暫存器(8086對特殊功能暫存器還有細分),通用暫存器,顧名思義是通用的,可以存盤運算元、運算結果、記憶體地址等資料,在使用C語言編程時,一般不直接接觸通用暫存器,而由編譯器負責安排其使用,特殊功能暫存器有特定的功能,一些作用于CPU內部,如PC存放下一條指令的地址,SP記錄記憶體中堆疊頂的位置(現在無需了解這些);另一些與IO模組相連接,單片機程式通過這類暫存器來控制各種外設,我們今天要學習的數字信號IO暫存器就屬于這一類,并且應該是其中最簡單的了,

我們使用的單片機的型號是ATmega324PA,它有多種封裝,引腳(pin)數不盡相同,但都有32個通用輸入輸出(GPIO)引腳,由于AVR架構是8位字長的,CPU一次處理1位資料和8位資料所需的時間是一樣的,這32個引腳被組織為4個埠(port),分別是PA、PB、PC和PD,

在AVR架構tiny與mega系列的單片機中,每個埠都有3個暫存器控制數字信號IO,分別是PORTx、DDRx和PINx,這里的x是A、B、C或D,由于這4個埠在數字IO方面完全相同,就把它們合并起來講,相應地,對于每個引腳Pxn,有PORTxn、DDxn(沒有R)和PINxn三個bit控制其數字IO,

DDxn控制引腳方向:當DDxn為1時,Pxn為輸出;當DDxn為0時,Pxn為輸入,

當Pxn為輸入時,如果PORTxn為1,則該引腳通過一個上拉電阻連接到VCC;否則引腳懸空,

當Pxn為輸出時,如果PORTxn為1,引腳輸出高電平;否則輸出低電平,

PINxn的值為Pxn引腳的電平,如果給PINxn寫入1,PORTxn的值會翻轉,

還有很多細節問題,如MCUCR暫存器中PUD位的功能、復位后暫存器的值、輸入輸出切換的方法、讀取引腳電平的延遲、未連接引腳的處理方法等,留作今天的作業,閱讀資料手冊I/O-Ports一章中除Alternate Port Functions一節以外的內容(一共8頁不到,不多吧),找出這些問題的答案,并以此為基礎回答上一篇教程最后的問題,

 

講了這么多,相信你也沒記住多少,而且你也不知道去哪里用這些暫存器,

要使用暫存器,你需要在C語言程式中寫 #include <avr/io.h> (在創建專案時自動生成的代碼中就有),然后就可以使用 PORTA 、 DDRB 、 PINC 等暫存器了,它們是宏定義,你不必去探究它們展開后是怎樣的,只需知道這些宏可以讀取,可以賦值,可以位操作,就像 uint8_t 型別變數一樣,

但是諸如 PORTA0 和 DDB7 等宏定義卻不代表暫存器上的那一位,它們實際上就是字面值常量,如 PORTAx 的意義是暫存器 PORTA 的第x位(第0位為最低位,第7位為最高位),它的值就是x,因此,直接對這些宏復制是不正確的(不僅意義不正確,編譯也不會通過),

在開發板的庫函式中的 <ee1/bit.h> 提供了包含幾個用于位操作的宏函式,我們先按照手冊來用,稍后來看它們是如何實作的,

 

我們先返璞歸真一下,回到最初的例子,點亮一個LED,不過這次我們不再使用 <ee1/led.h> 提供的函式,而是直接操作暫存器,

先點亮紅色LED吧,在原理圖的第2頁左上角,紅色LED通過一個電阻連接到網路LED0,而在第1頁中LED0連接的是單片機PC4引腳,因此我們需要讓PC4引腳輸出高電平,回到上面看一下三個暫存器的功能,輸出高電平需要DDxn和PORTxn同時為1,這里把x和n分別用C和4帶入,即我們要讓DDC4和PORTC4為1,

將一個暫存器的一位置為1可以由 set_bit 實作,它需要兩個引數,要操作的整型變數與表示第幾位的整數,把DDC4置為1應該寫 set_bit(DDRC, 4); ,4 可以用 DDC4 替換,這個定義就是這么用的,類似地也可以將PORTC4置為1,點亮紅色LED的整個程式如下:

1 #include <avr/io.h>2 #include <ee1/bit.h>3 4 int main(void)5 {6     set_bit(DDRC , 4);7     set_bit(PORTC, 4);8 }

相信聰明的你已經知道閃爍和流水燈怎么寫了,翻轉輸出電平可以使用 flip_bit(PORTC, 4); ,也可以使用 set_bit(PINC, 4); ,

 

下面來看數字輸入,還是用第一個與按鍵相關的例子,讓LED狀態與按鍵保持一致,即按下亮起,

讀取一個暫存器中的一位可以使用 read_bit,如果引腳上電平為高,read_bit 的運算結果非0(但不一定是布林值1),如果你沒有忘記的話,按鍵按下時引腳電平為低,因此對讀取引腳電平的結果取非才是按鍵是否按下,

在原理圖中,按鍵一端連接在BTN0網路上,進而連接到單片機的PA4引腳,因此按鍵是否按下應該寫為:!read_bit(PINA, 4) ,

在讀取之前應該先把引腳配置為輸入,盡管復位后默認為輸入,在這個例子中沒有必要向DDA4寫0,但明確寫出來可以讓看這段代碼的人(可能別人也可能是你自己)明白PA4是作輸入的,這樣做是一種良好的習慣,至于PORTA4,由于這一引腳在外部有連接上拉電阻,就沒有必要啟用內部上拉電阻了,

 1 #include <avr/io.h> 2 #include <ee1/bit.h> 3  4 int main(void) 5 { 6     reset_bit(DDRA, 4); 7     set_bit(DDRC, 4); 8     while (1) 9     {10         cond_bit(!read_bit(PINA, 4), PORTC, 4);11     }12 }

再結合按鍵動作的知識,你應該知道怎樣直接通過暫存器操作來判斷按鍵動作了吧,

順便說一句,以上兩個程式都不必在專案屬性中給linker加上libee1庫,雖然代碼中使用了 <ee1/bit.h> ,但其中都是宏定義,與linker無關,

 

之前留了一個問題,就是位操作是如何實作的,以下為 <ee1/bit.h> 中部分代碼:

1 #define set_bit  (r, b) ((r) |=  (1u << (b)))2 #define reset_bit(r ,b) ((r) &= ~(1u << (b)))3 #define read_bit (r, b) ((r) &   (1u << (b)))4 #define flip_bit (r, b) ((r) ^=  (1u << (b)))

寫那么多括號是為了防止出現運算子優先級的問題,假設r就是一個暫存器,比如PORTC,b就是一個數字,比如4,也可以是一個變數,那么 (r) |= (1u << (b)) 就相當于 r = r | 1u << b (后綴u表示無符號數,位操作的運算元一般都是無符號數),對于二進制表示下的每一位,如果不是第b位,那么位或運算子右邊此位為0,運算結果等于左邊,即r的這些為保持不變;對于第b位,右邊此位為1,無論左邊此位的值是多少,結果一定是1,即這一位被置1;這樣就實作了將一位置為1的功能,

reset_bit 的實作還要繞一個彎,1u << b 是一個第b位為1,其余位為0的數,那么 ~(1u << b) ,即位與賦值號右邊,是一個第b位為0而其余為都是1的數,仿照上面的分析可得,運算結果的第b位一定是0而其余位與r中原來的值相同,類似的分析也可應用于 flip_bit :兩個bit進行異或運算的結果,若相同則為1,不同則為0;當一個運算元是1時,結果就是另一個運算元取反;當一個運算元是0時,結果與另一個運算元相同;因此 flip_bit 就使r的第b為取反而其他為不變,

以上是向暫存器中的位寫入的操作,用于讀取位的 read_bit 的原理也大致相同,用暫存器的值與 1u << b 相與,僅當第b位為1時結果是 1u << b ,這是個非零數,否則結果為0,read_bit 陳述句可以直接放在 if 陳述句的條件部分,但如果是根據其結果決定一個變數是否加1,不能直接加上其運算結果,可以轉換成 bool 型別或用 if 陳述句判斷,

 

這篇教程有點長,好好消化一下,然后把以前寫過的程式用暫存器重新寫一遍,以此鞏固所學的知識,

 

從本教程開始至今,我們先了解了LED燈、按鍵、撥動開關、數字輸入輸出的使用方法,然后學習C語言位操作與數字IO暫存器,終于打通了一條從底層到應用的路,而網路上很多教程都是反過來講的,即先介紹暫存器,然后直接通過暫存器來驅動LED、檢測按鍵等,甚至有直接寫諸如 DDRB |= 0x0C; 或 if (PINB & 0x40) 這樣的代碼的,初學者怎么看得明白?站在我的角度,我覺得以上都是常識,都不用講,盡管我學習的時候也頗費周折(正是因為那些反過來的教程),現在我站在初學者的角度,認為本教程的講解順序是更容易理解的,

我學習計算機之前,總對計算機抱有特殊的幻想,覺得它什么都能干,很神奇,現在這些想法都沒有了,尤其是在學習單片機的程序中,學習計算機教會我們分析問題、解決問題,而學習單片機讓我們更好地理解計算機是如何按照我們的想法來解決問題的,這篇教程帶你了解了暫存器,在你學習單片機的全程序中,它都會伴隨著你,暫存器是硬體和軟體之間的一個重要紐帶,計算機的任何功能都離不開暫存器,CPU?有暫存器,總線通信?通過暫存器,記憶體分頁?需要暫存器,萬物基于暫存器,又有更多像暫存器一樣的紐帶,在電子空穴與豐富多彩的計算機世界之間建立起聯系,它們看起來如此復雜,卻又清晰明了,就算一夜之間所有計算機都突然消失,人類也能從電子管和打孔紙帶開始,一層一層地構建起計算機的世界,而我們了解的只不過是這個巨大體系中的滄海一粟,

初入計算機世界,你想著計算機能干什么,學完計算機我能干什么,而計算機世界是如此高深,在逐漸深入后,你會明白計算機不能干什么,我不能干什么,數碼管、蜂鳴器,它們一直在你的開發板上,你卻不知道如何使用它們;更不用說那些高級到你沒聽說過的東西,學得越多,你會發現雖然原本不會的減少了,但腦海中萌生出的“不切實際”的想法更多——學習的速度永遠趕不上認知的速度,本系列教程不能幫你消除所有“不會”,而是要在帶你一步步消除一些“不會”的程序中讓你學會發現更多“不會”并消除的方法,

 

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/71573.html

標籤:C

上一篇:C語言入門-字串

下一篇:Dart語言學習(八) dynamic

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more