C語言嵌入式系統程式設計修煉之道- IT閱讀

文章推薦指數: 80 %
投票人數:10人

著眼於討論普遍的嵌入式系統 C 程式設計技巧,系統的協議處理模組沒有選擇特別的 CPU ,而是選擇了眾所周知的 CPU 晶片—— 80186 ,每一位學習過《微機原理》 ... C語言嵌入式系統程式設計修煉之道 首頁 最新 HTML CSS JavaScript jQuery Python3 Python2 Java C C++ Go SQL 首頁 最新 Search C語言嵌入式系統程式設計修煉之道 2018-11-13254 分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。

分享知識,造福人民,實現我們中華民族偉大復興!                   C語言嵌入式系統程式設計修煉之道——背景篇...1 C語言嵌入式系統程式設計修煉之道——軟體架構篇...4 1.模組劃分...4 2.多工還是單任務...5 3.單任務程式典型架構...6 4.中斷服務程式...7 5.硬體驅動模組...9 6.C的面向物件化...10 總結...10 C語言嵌入式系統程式設計修煉之道——記憶體操作篇...12 1.資料指標...12 2.函式指標...13 3.陣列vs.動態申請...14 4.關鍵字const15 5.關鍵字volatile.16 6.CPU字長與儲存器位寬不一致處理...17 總結...18 C語言嵌入式系統程式設計修煉之道——螢幕操作篇...19 1.漢字處理...19 2.系統時間顯示...20 3.動畫顯示...21 4.選單操作...22 5.模擬MessageBox函式...24 總結...26 C語言嵌入式系統程式設計修煉之道——鍵盤操作篇...27 1.處理功能鍵...27 2.處理數字鍵...28 3.整理使用者輸入...29 總結...30 C語言嵌入式系統程式設計修煉之道——效能優化篇...31 1.使用巨集定義...31 2.使用暫存器變數...31 3.內嵌彙編...32 4.利用硬體特性...32 5.活用位操作...33 總結     C 語言嵌入式系統程式設計修煉之道——背景篇 不同於一般形式的軟體程式設計,嵌入式系統程式設計建立在特定的硬體平臺上,勢必要求其程式語言具備較強的硬體直接操作能力。

無疑,組合語言具備這樣的特質。

但是,歸因於組合語言開發過程的複雜性,它並不是嵌入式系統開發的一般選擇。

而與之相比, C 語言——一種“高階的低階”語言,則成為嵌入式系統開發的最佳選擇。

筆者在嵌入式系統專案的開發過程中,一次又一次感受到 C 語言的精妙,沉醉於 C 語言給嵌入式開發帶來的便利。

本文的目的在於進行“ C 語言嵌入式系統開發的內功心法”秀,一共包括 25 招。

圖 1 給出了本文的討論所基於的硬體平臺,實際上,這也是大多數嵌入式系統的硬體平臺。

它包括兩部分: ( 1 )      以通用處理器為中心的協議處理模組,用於網路控制協議的處理; ( 2 )      以數字訊號處理器( DSP )為中心的訊號處理模組,用於調製、解調和數 / 模訊號轉換。

本文的討論主要圍繞以通用處理器為中心的協議處理模組進行,因為它更多地牽涉到具體的 C 語言程式設計技巧。

而 DSP 程式設計則重點關注具體的數字訊號處理演算法,主要涉及通訊領域的知識,不是本文的討論重點。

著眼於討論普遍的嵌入式系統 C 程式設計技巧,系統的協議處理模組沒有選擇特別的 CPU ,而是選擇了眾所周知的 CPU 晶片—— 80186 ,每一位學習過《微機原理》的讀者都應該對此晶片有一個基本的認識,且對其指令集比較熟悉。

80186 的字長是 16 位,可以定址到的記憶體空間為 1MB ,只有實地址模式。

C 語言編譯生成的指標為 32 位(雙字),高 16 位為段地址,低 16 位為段內編譯,一段最多 64KB 。

圖 1  系統硬體架構 協議處理模組中的 FLASH 和 RAM 幾乎是每個嵌入式系統的必備裝置,前者用於儲存程式,後者則是程式執行時指令及資料的存放位置。

系統所選擇的 FLASH 和 RAM 的位寬都為 16 位,與 CPU 一致。

實時鐘晶片可以為系統定時,給出當前的年、月、日及具體時間(小時、分、秒及毫秒),可以設定其經過一段時間即向 CPU 提出中斷或設定報警時間到來時向 CPU 提出中斷(類似鬧鐘功能)。

NVRAM (非易失去性 RAM )具有掉電不丟失資料的特性,可以用於儲存系統的設定資訊,譬如網路協議引數等。

在系統掉電或重新啟動後,仍然可以讀取先前的設定資訊。

其位寬為 8 位,比 CPU 字長小。

文章特意選擇一個與 CPU 字長不一致的儲存晶片,為後文中一節的討論創造條件。

UART 則完成 CPU 並行資料傳輸與 RS-232 序列資料傳輸的轉換,它可以在接收到 [1~MAX_BUFFER] 位元組後向 CPU 提出中斷, MAX_BUFFER 為 UART 晶片儲存接收到位元組的最大緩衝區。

鍵盤控制器和顯示控制器則完成系統人機介面的控制。

以上提供的是一個較完備的嵌入式系統硬體架構,實際的系統可能包含更少的外設。

之所以選擇一個完備的系統,是為了後文更全面的討論嵌入式系統 C 語言程式設計技巧的方方面面,所有裝置都會成為後文的分析目標。

嵌入式系統需要良好的軟體開發環境的支援,由於嵌入式系統的目標機資源受限,不可能在其上建立龐大、複雜的開發環境,因而其開發環境和目標執行環境相互分離。

因此,嵌入式應用軟體的開發方式一般是,在宿主機 (Host) 上建立開發環境,進行應用程式編碼和交叉編譯,然後宿主機同目標機 (Target) 建立連線,將應用程式下載到目標機上進行交叉除錯,經過除錯和優化,最後將應用程式固化到目標機中實際執行。

CAD-UL 是適用於 x86 處理器的嵌入式應用軟體開發環境,它執行在 Windows 作業系統之上,可生成 x86 處理器的目的碼並通過 PC 機的 COM 口( RS-232 串列埠)或乙太網口下載到目標機上執行,如圖 2 。

其駐留於目標機 FLASH 儲存器中的 monitor 程式可以監控宿主機 Windows 除錯平臺上的使用者除錯指令,獲取 CPU 暫存器的值及目標機儲存空間、 I/O 空間的內容。

圖 2  交叉開發環境 後續章節將從軟體架構、記憶體操作、螢幕操作、鍵盤操作、效能優化等多方面闡述 C 語言嵌入式系統的程式設計技巧。

軟體架構是一個巨集觀概念,與具體硬體的聯絡不大;記憶體操作主要涉及系統中的 FLASH 、 RAM 和 NVRAM 晶片;螢幕操作則涉及顯示控制器和實時鐘;鍵盤操作主要涉及鍵盤控制器;效能優化則給出一些具體的減小程式時間、空間消耗的技巧。

本文即將講述的 25 個主題可分為兩類,一類是程式設計技巧,有很強的適用性;一類則介紹嵌入式系統程式設計的一般常識,具有一定的理論意義。

So,let’sgo.   C語言嵌入式系統程式設計修煉之道——軟體架構篇 1.模組劃分 模組劃分的“劃”是規劃的意思,意指怎樣合理的將一個很大的軟體劃分為一系列功能獨立的部分合作完成系統的需求。

C 語言作為一種結構化的程式設計語言,在模組的劃分上主要依據功能(依功能進行劃分在面向物件設計中成為一個錯誤,牛頓定律遇到了相對論), C 語言模組化程式設計需理解如下概念: ( 1 )     模組即是一個 .c 檔案和一個 .h 檔案的結合,標頭檔案 (.h) 中是對於該模組介面的宣告; ( 2 )     某模組提供給其它模組呼叫的外部函式及資料需在 .h 中檔案中冠以 extern 關鍵字宣告; ( 3 )     模組內的函式和全域性變數需在 .c 檔案開頭冠以 static 關鍵字宣告; ( 4 )     永遠不要在 .h 檔案中定義變數!定義變數和宣告變數的區別在於定義會產生記憶體分配的操作,是彙編階段的概念;而宣告則只是告訴包含該宣告的模組在連線階段從其它模組尋找外部函式和變數。

如: /*module1.h*/ inta=5;              /* 在模組 1 的 .h 檔案中定義 inta */     /*module1.c*/ #include“module1.h”    /* 在模組 1 中包含模組 1 的 .h 檔案 */  /*module2.c*/ #include“module1.h”    /* 在模組 2 中包含模組 1 的 .h 檔案 */  /*module3.c*/ #include“module1.h”    /* 在模組 3 中包含模組 1 的 .h 檔案 */ 以上程式的結果是在模組 1 、 2 、 3 中都定義了整型變數 a , a 在不同的模組中對應不同的地址單元,這個世界上從來不需要這樣的程式。

正確的做法是: /*module1.h*/ externinta;              /* 在模組 1 的 .h 檔案中宣告 inta */  /*module1.c*/ #include“module1.h”       /* 在模組 1 中包含模組 1 的 .h 檔案 */ inta=5;                /* 在模組 1 的 .c 檔案中定義 inta */   /*module2.c*/ #include“module1.h”       /* 在模組 2 中包含模組 1 的 .h 檔案 */   /*module3.c*/ #include“module1.h”    /* 在模組 3 中包含模組 1 的 .h 檔案 */ 這樣如果模組 1 、 2 、 3 操作 a 的話,對應的是同一片記憶體單元。

一個嵌入式系統通常包括兩類模組: ( 1 )硬體驅動模組,一種特定硬體對應一個模組; ( 2 )軟體功能模組,其模組的劃分應滿足低偶合、高內聚的要求。

2.多工還是單任務 所謂“單任務系統”是指該系統不能支援多工併發操作,巨集觀序列地執行一個任務。

而多工系統則可以巨集觀並行(微觀上可能序列)地“同時”執行多個任務。

多工的併發執行通常依賴於一個多工作業系統( OS ),多工 OS 的核心是系統排程器,它使用任務控制塊( TCB )來管理任務排程功能。

TCB 包括任務的當前狀態、優先順序、要等待的事件或資源、任務程式碼的起始地址、初始堆疊指標等資訊。

排程器在任務被啟用時,要用到這些資訊。

此外, TCB 還被用來存放任務的“上下文”( context) 。

任務的上下文就是當一個執行中的任務被停止時,所要儲存的所有資訊。

通常,上下文就是計算機當前的狀態,也即各個暫存器的內容。

當發生任務切換時,當前執行的任務的上下文被存入 TCB ,並將要被執行的任務的上下文從它的 TCB 中取出,放入各個暫存器中。

嵌入式多工 OS 的典型例子有 Vxworks 、 ucLinux 等。

嵌入式 OS 並非遙不可及的神壇之物,我們可以用不到 1000 行程式碼實現一個針對 80186 處理器的功能最簡單的 OS 核心,作者正準備進行此項工作,希望能將心得貢獻給大家。

究竟選擇多工還是單任務方式,依賴於軟體的體系是否龐大。

例如,絕大多數手機程式都是多工的,但也有一些小靈通的協議棧是單任務的,沒有作業系統,它們的主程式輪流呼叫各個軟體模組的處理程式,模擬多工環境。

3.單任務程式典型架構 ( 1 )從 CPU 復位時的指定地址開始執行; ( 2 )跳轉至彙編程式碼 startup 處執行; ( 3 )跳轉至使用者主程式 main 執行,在 main 中完成: a. 初試化各硬體裝置;   b. 初始化各軟體模組; c. 進入死迴圈(無限迴圈),呼叫各模組的處理函式     使用者主程式和各模組的處理函式都以 C 語言完成。

使用者主程式最後都進入了一個死迴圈,其首選方案是: while(1) { } 有的程式設計師這樣寫: for(;;) { } 這個語法沒有確切表達程式碼的含義,我們從 for(;;) 看不出什麼,只有弄明白 for(;;) 在 C 語言中意味著無條件迴圈才明白其意。

下面是幾個“著名”的死迴圈: ( 1 )作業系統是死迴圈; ( 2 ) WIN32 程式是死迴圈; ( 3 )嵌入式系統軟體是死迴圈; ( 4 )多執行緒程式的執行緒處理函式是死迴圈。

你可能會辯駁,大聲說:“凡事都不是絕對的, 2 、 3 、 4 都可以不是死迴圈”。

Yes , youareright ,但是你得不到鮮花和掌聲。

實際上,這是一個沒有太大意義的牛角尖,因為這個世界從來不需要一個處理完幾個訊息就喊著要 OS 殺死它的 WIN32 程式,不需要一個剛開始 RUN 就自行了斷的嵌入式系統,不需要莫名其妙啟動一個做一點事就幹掉自己的執行緒。

有時候,過於嚴謹製造的不是便利而是麻煩。

君不見,五層的 TCP/IP 協議棧超越嚴謹的 ISO/OSI 七層協議棧大行其道成為事實上的標準? 經常有網友討論: printf(“%d,%d”,++i,i++);   /* 輸出是什麼? */ c=a+++b ;              /*c=?*/ 等類似問題。

面對這些問題,我們只能發出由衷的感慨:世界上還有很多有意義的事情等著我們去消化攝入的食物。

實際上,嵌入式系統要執行到世界末日。

4.中斷服務程式 中斷是嵌入式系統中重要的組成部分,但是在標準 C 中不包含中斷。

許多編譯開發商在標準 C 上增加了對中斷的支援,提供新的關鍵字用於標示中斷服務程式 (ISR) ,類似於 __interrupt 、 #programinterrupt 等。

當一個函式被定義為 ISR 的時候,編譯器會自動為該函式增加中斷服務程式所需要的中斷現場入棧和出棧程式碼。

中斷服務程式需要滿足如下要求: (1) 不能返回值; (2) 不能向 ISR 傳遞引數; (3)ISR 應該儘可能的短小精悍; (4)printf(char*lpFormatString,…) 函式會帶來重入和效能問題,不能在 ISR 中採用。

在某專案的開發中,我們設計了一個佇列,在中斷服務程式中,只是將中斷型別新增入該佇列中,在主程式的死迴圈中不斷掃描中斷佇列是否有中斷,有則取出佇列中的第一個中斷型別,進行相應處理。

/* 存放中斷的佇列 */ typedefstructtagIntQueue { intintType;               /* 中斷型別 */ structtagIntQueue*next; }IntQueue;    IntQueuelpIntQueueHead;    __interruptISRexample() {   int intType;  intType=GetSystemType(); QueueAddTail(lpIntQueueHead,intType) ; /* 在佇列尾加入新的中斷 */  } 在主程式迴圈中判斷是否有中斷: While(1) { If(!IsIntQueueEmpty())   {    intType=GetFirstInt();    switch(intType)     /*  是不是很象 WIN32 程式的訊息解析函式 ? */     {               /*  對,我們的中斷型別解析很類似於訊息驅動 */     casexxx:         /* 我們稱其為“中斷驅動”吧? */      …     break;     casexxx:      …     break;     …     } }    } 按上述方法設計的中斷服務程式很小,實際的工作都交由主程式執行了。

5.硬體驅動模組 一個硬體驅動模組通常應包括如下函式: ( 1 )中斷服務程式 ISR ( 2 )硬體初始化 a. 修改暫存器,設定硬體引數(如 UART 應設定其波特率, AD/DA 裝置應設定其取樣速率等); b. 將中斷服務程式入口地址寫入中斷向量表: /* 設定中斷向量表 */  m_myPtr=make_far_pointer(0l);/*  返回 voidfar 型指標 voidfar* */      m_myPtr+=ITYPE_UART; /* ITYPE_UART : uart 中斷服務程式 */ /*  相對於中斷向量表首地址的偏移 */  *m_myPtr=&UART_Isr;  /*UART_Isr : UART 的中斷服務程式 */ ( 3 )設定 CPU 針對該硬體的控制線 a. 如果控制線可作 PIO (可程式設計 I/O )和控制訊號用,則設定 CPU 內部對應暫存器使其作為控制訊號; b. 設定 CPU 內部的針對該裝置的中斷遮蔽位,設定中斷方式(電平觸發還是邊緣觸發)。

( 4 )提供一系列針對該裝置的操作介面函式。

例如,對於 LCD ,其驅動模組應提供繪製畫素、畫線、繪製矩陣、顯示字元點陣等函式;而對於實時鐘,其驅動模組則需提供獲取時間、設定時間等函式。

6.C的面向物件化 在面向物件的語言裡面,出現了類的概念。

類是對特定資料的特定操作的集合體。

類包含了兩個範疇:資料和操作。

而 C 語言中的 struct 僅僅是資料的集合,我們可以利用函式指標將 struct 模擬為一個包含資料和操作的“類”。

下面的 C 程式模擬了一個最簡單的“類”: #ifndef C_Class       #defineC_Classstruct #endif C_ClassA {       C_ClassA*A_this;            /*this 指標 */       void(*Foo)(C_ClassA*A_this); /* 行為:函式指標 */       inta;                       /* 資料 */       intb; }; 我們可以利用 C 語言模擬出面向物件的三個特性:封裝、繼承和多型,但是更多的時候,我們只是需要將資料與行為封裝以解決軟體結構混亂的問題。

C 模擬面向物件思想的目的不在於模擬行為本身,而在於解決某些情況下使用 C 語言程式設計時程式整體框架結構分散、資料和函式脫節的問題。

我們在後續章節會看到這樣的例子。

總結 本篇介紹了嵌入式系統程式設計軟體架構方面的知識,主要包括模組劃分、多工還是單任務選取、單任務程式典型架構、中斷服務程式、硬體驅動模組設計等,從巨集觀上給出了一個嵌入式系統軟體所包含的主要元素。

請記住:軟體結構是軟體的靈魂!結構混亂的程式面目可憎,除錯、測試、維護、升級都極度困難。

一個高尚的程式設計師應該是寫出如藝術作品般程式的程式設計師。

  C語言嵌入式系統程式設計修煉之道——記憶體操作篇 1.資料指標 在嵌入式系統的程式設計中,常常要求在特定的記憶體單元讀寫內容,彙編有對應的 MOV 指令,而除 C/C++ 以外的其它程式語言基本沒有直接訪問絕對地址的能力。

在嵌入式系統的實際除錯中,多借助 C 語言指標所具有的對絕對地址單元內容的讀寫能力。

以指標直接操作記憶體多發生在如下幾種情況: (1)   某 I/O 晶片被定位在 CPU 的儲存空間而非 I/O 空間,而且暫存器對應於某特定地址; (2)   兩個 CPU 之間以雙埠 RAM 通訊, CPU 需要在雙埠 RAM 的特定單元(稱為 mailbox )書寫內容以在對方 CPU 產生中斷; (3)   讀取在 ROM 或 FLASH 的特定單元所燒錄的漢字和英文字模。

譬如: unsignedchar*p=(unsignedchar*)0xF000FF00; *p=11; 以上程式的意義為在絕對地址 0xF0000+0xFF00(80186 使用 16 位段地址和 16 位偏移地址 ) 寫入 11 。

在使用絕對地址指標時,要注意指標自增自減操作的結果取決於指標指向的資料類別。

上例中 p++ 後的結果是 p=0xF000FF01 ,若 p 指向 int ,即: int*p=(int*)0xF000FF00; p++( 或 ++p) 的結果等同於: p= p+sizeof(int) ,而 p—( 或 —p) 的結果是 p= p-sizeof(int) 。

同理,若執行: longint*p=(longint*)0xF000FF00; 則 p++( 或 ++p) 的結果等同於: p= p+sizeof(longint) ,而 p—( 或 —p) 的結果是 p= p-sizeof(longint) 。

記住: CPU 以位元組為單位編址,而 C 語言指標以指向的資料型別長度作自增和自減。

理解這一點對於以指標直接操作記憶體是相當重要的。

2.函式指標 首先要理解以下三個問題: ( 1 ) C 語言中函式名直接對應於函式生成的指令程式碼在記憶體中的地址,因此函式名可以直接賦給指向函式的指標; 相關文章 C語言嵌入式系統程式設計修煉之道 C語言嵌入式系統程式設計修煉之軟體架構篇 C語言嵌入式系統程式設計修煉之三:記憶體操作 [讀書筆記3]《C語言嵌入式系統程式設計修煉》 [讀書筆記2]《C語言嵌入式系統程式設計修煉》 C語言,嵌入式,資料結構面試題目(3) 簡圖記錄-C語言嵌入式測試驅動開發基礎 C語言,嵌入式,資料結構面試題目(1) 嵌入式高手修煉之路,想月薪30K就努力學 IARC語言嵌入彙編問題 C語言的型別系統-編碼,型別轉換及其規範 C語言中呼叫系統命令(systempopen...) 用C語言實現Linux系統的cp指令 201402579《嵌入式系統程序設計》第七周學習總結 2014025670(12)《嵌入式系統程序設計》第七周學習總結 分類導航 HTML/CSS HTML教程 HTML5教程 CSS教程 CSS3教程 JavaScript JavaScript教程 jQuery教程 Node.js教程 服務端 Python教程 Python3教程 Linux教程 Docker教程 Ruby教程 Java教程 JSP教程 C教程 C++教程 Perl教程 Go教程 PHP教程 正則表達式 資料庫 SQL教程 MySQL教程 PostgreSQL教程 SQLite教程 MongoDB教程 Redis教程 Memcached教程 行動端 IOS教程 Swift教程 Advertisement 三度辭典 Copyright©2016-2021IT閱讀  Itread01.comAllRightsReserved. 0.001291036605835



請為這篇文章評分?