單片機C語言模塊化編程方法

2021-01-11 電子工程世界

當你在一個項目小組做一個相對較複雜的工程時,意味著你不再獨自單幹。你需要和你的小組成員分工合作,一起完成項目,這就要求小組成員各自負責一部分工程。比如你可能只是負責通訊或者顯示這一塊。這個時候,你就應該將自己的這一塊程序寫成一個模塊,單獨調試,留出接口供其它模塊調用。最後,小組成員都將自己負責的模塊寫完並調試無誤後,由項目組長進行組合調試。像這些場合就要求程序必須模塊化。模塊化的好處是很多的,不僅僅是便於分工,它還有助於程序的調試,有利於程序結構的劃分,還能增加程序的可讀性和可移植性。
初學者往往搞不懂如何模塊化編程,其實它是簡單易學,而且又是組織良好程序結構行之有效的方法之一.
本文將先大概講一下模塊化的方法和注意事項,最後將以初學者使用最廣的keil c編譯器為例,給出模塊化編程的詳細步驟。
模塊化程序設計應該理解以下概述:
(1) 模塊即是一個.c 文件和一個.h 文件的結合,頭文件(.h)中是對於該模塊接口的聲明;
這一條概括了模塊化的實現方法和實質:將一個功能模塊的代碼單獨編寫成一個.c文件,然後把該模塊的接口函數放在.h文件中.舉例:假如你用到液晶顯示,那麼你可能會寫一個液晶驅動模塊,以實現字符、漢字和圖像的現實,命名為: led_device.c,該模塊的.c文件大體可以寫成:

#include …

//定義變量
unsigned char value;//全局變量

//定義函數
//這是本模塊第一個函數,起到延時作用,只供本模塊的函數調用,所以用到static關鍵字修飾

static void delay (uint us) //delay time
{}
//這是本模塊的第二個函數,要在其他模塊中調用

void wr_lcd (uchar dat_comm,uchar content)
{}
……
……

註:此處只寫出這兩個函數,第一個延時函數的作用範圍是模塊內,第二個,它是其它模塊需要的。為了簡化,此處並沒有寫出函數體.

.h文件中給出模塊的接口.在上面的例子中,向LCD寫入字符函數:wr_lcd (uchar dat_comm,uchar content)就是一個接口函數,因為其它模塊會調用它,那麼.h文件中就必須將這個函數聲明為外部函數(使用extrun關鍵字修飾),另一個延時函數:void delay (uint us)只是在本模塊中使用(本地函數,用static關鍵字修飾),因此它是不需要放到.h文件中的。
.h文件格式如下:

//聲明全局變量
extern unsigned char value;
//聲明接口函數
extern void wr_lcd (uchar dat_comm,uchar content); //向LCD寫入字符
……

這裡注意三點:
1. 在keil 編譯器中,extern這個關鍵字即使不聲明,編譯器也不會報錯,且程序運行良好,但不保證使用其它編譯器也如此。強烈建議加上,養成良好的編程規範。
2. .c文件中的函數只有其它模塊使用時才會出現在.h文件中,像本地延時函數static void delay (uint us)即使出現在.h文件中也是在做無用功,因為其它模塊根本不去調用它,實際上也調用不了它(static關鍵字的限制作用)。
3.注意本句最後一定要加分號」;」,相信有不少同學遇到過這個奇怪的編譯器報錯: error C132: 'xxxx': not in formal parameter list,這個錯誤其實是.h的函數聲明的最後少了分號的緣故。

模塊的應用:假如需要在LCD菜單模塊lcd_menu.c中使用液晶驅動模塊lcd_device.c中的函數void wr_lcd (uchar dat_comm,uchar content),只需在LCD菜單模塊的lcd_menu.c文件中加入液晶驅動模塊的頭文件lcd_device.h即可.

#include「lcd_device.h //包含液晶驅動程序頭文件,之後就可以在該.c文件中調用//lcd_device.h中的全局函數,使用液晶驅動程序裡的全局//變量(如果有的話)。

//調用向LCD寫入字符函數
wr_lcd (0x01,0x30);

//對全局變量賦值
value=0xff;

(2) 某模塊提供給其它模塊調用的外部函數及數據需在.h 中文件中冠以extern 關鍵字聲明;
這句話在上面的例子中已經有體現,即某模塊提供給其它模塊調用的外部函數和全局變量需在.h 中文件中冠以extern 關鍵字聲明,下面重點說一下全局變量的使用。使用模塊化編程的一個難點(相對於新手)就是全局變量的設定,初學者往往很難想通模塊與模塊公用的變量是如何實現的,常規的做法就是本句提到的,在.h文件中外部數據冠以extern關鍵字聲明。比如上例的變量value就是一個全局變量,若是某個模塊也使用這個變量,則和使用外部函數一樣,只需在使用的模塊.c文件中包含#include「lcd_device.h」即可。
另一種處理模塊間全局變量的方法來自於嵌入式作業系統uCOS-II,這個作業系統處理全局變量的方法比較特殊,也比較難以理解,但學會之後妙用無窮,這個方法只需用在頭文件中定義一次。方法為:
在定義所有全局變量(uCOS-II將所有全局變量定義在一個.h文件內)的.h頭文件中:
#ifdef xxx_GLOBALS
#define xxx_EXT
#else
#define xxx_EXT extern
#endif
.H 文件中每個全局變量都加上了xxx_EXT的前綴。xxx 代表模塊的名字。
該模塊的.C文件中有以下定義:
#define xxx_GLOBALS
#include "includes.h"
當編譯器處理.C文件時,它強制xxx_EXT(在相應.H文件中可以找到)為空,(因為xxx_GLOBALS已經定義)。所以編譯器給每個全局變量分配內存空間,而當編譯器處理其他.C 文件時,xxx_GLOBAL沒有定義,xxx_EXT 被定義為extern,這樣用戶就可以調用外部全局變量。為了說明這個概念,可以參見uC/OS_II.H,其中包括以下定義:
#ifdef OS_GLOBALS
#define OS_EXT
#else
#define OS_EXT extern
#endif
OS_EXT INT32U OSIdleCtr;
OS_EXT INT32U OSIdleCtrRun;
OS_EXT INT32U OSIdleCtrMax;
同時,uCOS_II.H 有中以下定義:
#define OS_GLOBALS
#include 「includes.h」
當編譯器處理uCOS_II.C 時,它使得頭文件變成如下所示,因為OS_EXT 被設置為空。
INT32U OSIdleCtr;
INT32U OSIdleCtrRun;
INT32U OSIdleCtrMax;
這樣編譯器就會將這些全局變量分配在內存中。當編譯器處理其他.C 文件時,頭文件變成了如下的樣子,因為OS_GLOBAL沒有定義,所以OS_EXT 被定義為extern。
extern INT32U OSIdleCtr;
extern INT32U OSIdleCtrRun;
extern INT32U OSIdleCtrMax;
在這種情況下,不產生內存分配,而任何 .C文件都可以使用這些變量。這樣的就只需在 .H文件中定義一次就可以了。
(3) 模塊內的函數和全局變量需在.c 文件開頭冠以static 關鍵字聲明;
這句話主要講述了關鍵字static的作用。Static是一個相當重要的關鍵字,他能對函數和變量做一些約束,而且可以傳遞一些信息。比如上例在LCD驅動模塊.c文件中定義的延時函數static void delay (uint us),這個函數冠以static修飾,一方面是限定了函數的作用範圍只是在本模塊中起作用,另一方面也給人傳達這樣的信息:該函數不會被其他模塊調用。下面詳細說一下這個關鍵字的作用,在C 語言中,關鍵字static 有三個明顯的作用:
1.在函數體,一個被聲明為靜態的變量在這一函數被調用過程中維持其值不變。
2.在模塊內(但在函數體外),一個被聲明為靜態的變量可以被模塊內所用函數訪問,但不能被模塊外其它函數訪問。它是一個本地的全局變
量。
3.在模塊內,一個被聲明為靜態的函數只可被這一模塊內的其它函數調用。那就是,這個函數被限制在聲明它的模塊的本地範圍內使用。
前兩個都比較容易理解,最後一個作用就是剛剛舉例中提到的延時函數(static void delay (uint us)),本地化函數是有相當好的作用的。
(4) 永遠不要在.h 文件中定義變量!
呵呵,似乎有點危言聳聽的感覺,但我想也不會有多少人會在.h文件中定義變量的。
比較一下代碼:
代碼一:

int a = 5;

#include "module1.h"


#include "module1.h"


#include "module1.h"
以上程序的結果是在模塊1、2、3 中都定義了整型變量a,a 在不同的模塊中對應不同的地址元,這個世界上從來不需要這樣的程序。正確的做法是:
代碼二:

extern int a;


#include "module1.h"
int a = 5;


#include "module1.h"


#include "module1.h"
這樣如果模塊1、2、3 操作a 的話,對應的是同一片內存單元。
註:
一個嵌入式系統通常包括兩類(注意是兩類,不是兩個)模塊:
(1)硬體驅動模塊,一種特定硬體對應一個模塊;
(2)軟體功能模塊,其模塊的劃分應滿足低偶合、高內聚的要求。
下面以keil C 編譯器為例,講一下模塊化編程的步驟。
下面這個程序分為三層,共7個模塊,共同為主程序服務(它們之間也會相互調用)。
程序的結構圖如下所示:


程序主要模塊和功能簡介:
一. 底層驅動
1. 紅外鍵盤:程序通過紅外鍵盤進行操作。紅外鍵盤獨佔定時器0和外部中斷0,以實現紅外解碼和鍵盤鍵值的識別。紅外鍵盤定義了五個按鍵,分別為上翻、下翻、左翻、右翻和確認鍵。
2. LCD液晶顯示:程序主要通過LCD顯示信息,LCD液晶顯示驅動提供顯示漢字、圖形和ASCII碼的函數接口。可以全屏、單行顯示漢字,任意位置顯示ASCII碼,還可以全屏、半屏顯示圖形。
二. 功能模塊
1. LCD菜單程序:菜單程序可以使人機互動更加方便、容易。本菜單程序的菜單級別深度受RAM大小的限制,每增加一級菜單將多消耗4位元組的RAM。菜單程序主要完成菜單功能函數的調度,LCD顯示刷新。
2. 計算器程序:實現65536以內的加、減、乘、除,超出範圍會出現溢出,溢出發生時,LCD顯示「錯誤:出現溢出」的錯誤提示,同時本次運算被忽略。對於負數會顯示「-」號,除數為零時LCD顯示「錯誤:除數為零」的錯誤提示。
3. 開機次數記憶程序:主要對基於IIC總線的EEPROM進行讀寫,單片機每次上電後,將開機次數寫入EEPROM.
4. 串口測試程序:進入該程序後,單片機向電腦發送字符串「Hello Word!」,發送數字24(以字符的形式顯示)。編寫此程序的目的是為了能夠方便的向電腦發送字符串和變量,便於程序的調試。串口佔用串口資源,與頻率測量程序共享定時器1
5. 頻率測量:復用定時器1,佔用外部中斷1,實現5~20KHZ頻率的測量.
三. 主程序
主程序主要完成程序的初始化,LCD菜單顯示,監視鍵盤程序並根據鍵值更新菜單。
步驟為:
1.新建工程。
2.點擊File—New(或者點擊快捷圖標:),新建一個文檔。
3.點擊File—Save(或者點擊快捷圖標:),保存新建的文檔,在文件名後填寫LCD_device.c(液晶驅動模塊: LCD_device,提供顯示漢字、字符和圖像的接口),點擊確定。
在該文檔內編寫LCD驅動程序。
4. 點擊File—New(或者點擊快捷圖標:),再新建一個文檔。
5. 點擊File—Save(或者點擊快捷圖標:),保存新建的文檔,在文件名後填寫LCD_device.h(液晶驅動模塊的頭文件,模塊的接口和全局變量在這裡定義)。點擊確定。在該文檔中整理全局變量和接口函數。以上步驟之後的效果見下圖:

至此,液晶驅動模塊書寫完畢,可以對這個模塊單獨的調試。
6.重複以上步驟2~5,定義 紅外鍵盤模塊:key.c與key.h
菜單模塊:menu.c與menu.h
串口通信模塊:uart_.c與uart.h
計算器模塊:counter.c與counter.h
頻率測量模塊:mea_fre.c與mea_fre.h
開機次數記憶模塊:eepram.c與eepram.h
7.重複以上步驟2~3,定義主程序main.c
最終效果如下圖所示:

完成1~7個步驟後,有些小白就習慣性的點擊編譯按鈕了,這時候會出現兩個警告信息:
*** WARNING L1: UNRESOLVED EXTERNAL SYMBOL
*** WARNING L2: REFERENCE MADE TO UNRESOLVED EXTERNAL
這是因為你只是編寫好了程序模塊,卻沒有把他們加入到工程的緣故。
解決方法:在Project Workspace框中,右擊Source group 1文件夾,選擇Add Files to Group『Source Group 1』,在彈出的對話框中添加你的.c文件即可。

關鍵字:單片機  C語言  模塊化編程 編輯:什麼魚 引用地址:http://news.eeworld.com.cn/mcu/article_2017112036122.html

推薦閱讀

51單片機學習實踐:用NTC電阻測溫並顯示在TM1637數碼管

STC15單片機實驗名稱:使用NTC電阻測量溫度實驗內容:        使用NTC電阻測量溫度並顯示在數碼管上        讀取DS18B20模塊測量溫度作為參考實驗器材:        STC15W408AS_DIP16 x1        DS18B20  x1        TM1637數碼管 x1

發表於 2020-12-17

一款51單片機電子鐘製作(C語言)

,mao,ding,ding1,ding3,yin;//變量定義sbit ge=P0^0;//數碼管個位sbit shi4=P0^1;//數碼管十位sbit bai=P0^2;//數碼管百位sbit qian=P0^3;//數碼管千位sbit dian=P0^4;//兩小數點位sbit bbt=P3^5;//鬧鐘輸出口sbit key=P3^2;//功能按鍵定義sbit key1=P3^3;//++按鍵定義sbit key2=P3^4;//--按鍵定義bit  a=0;//調時間功能時位定義bit  b=0;//調小時位定義bit  c=0;//調分鐘位定義bit&nbsp

發表於 2020-12-17

51單片機+1602+DS18B20的溫度報警程序

51單片機做的溫度顯示,溫度顯示在LCD1602液晶屏上。然後按鍵可以調整溫度閾值,溫度高於或低於所設溫度,蜂鳴器就會響……單片機源程序如下:/***************************************************************************************                              基於單片機的實時溫度報警系統設計        &nbsp

發表於 2020-12-17

簡易星三角啟動電機 單片機C51程序

當電機較大時,啟動是個必須注意的問題。比較簡單且用的很多的是星三角啟動,這裡寫出了星三角啟動的核心部分。每一個IO口接上繼電器,再控制交流接觸器,就OK了。但,要注意,星、角交流接觸器一定要互鎖,否則一旦某個交流接觸器粘連,就造成短路了。#include <reg51.h>sbit K1=P3^1;  //按鍵1,控制第1個繼電器啟動sbit K2=P3^2;  //按鍵2,控制第2個繼電器啟動sbit J1=P1^5;  //星接繼電器sbit J2=P1^6;  //星點繼電器sbit J3=P1^7;  //角繼電器void Delay_1ms(unsigned i

發表於 2020-12-16

紅外熱釋電傳感器單片機設計

、技術性能穩定等特點而受到廣大用戶和專業人士的歡迎。而本設計的電路包括硬體和軟體兩個部分。硬體部分包括紅外感應部分與單片機控制部分。整個系統電路可劃分為:電源部分、傳感器模塊部分、單片機控制電路,而單片機控制由最小系統和指示燈電路、報警電路等子模塊組成。主要工作由熱釋電紅外感應器完成信息採集、處理、數據傳送經過單片機功能設定到達報警模塊這一過程。就此設計的核心模塊來說,單片機就是設計的中心單元。單片機應用系統也是由硬體和軟體組成。硬體包括單片機、輸入/輸出設備、以及外圍應用電路等組成的系統,軟體是主要是工作的程序通過編寫程序來控制輸入的信號。二、設計任務分析1.該設計包括硬體和軟體設計兩個部分。模塊劃分為數據採集、按鍵設定、報警

發表於 2020-12-16

STC15F104W單片機四路按鍵循環開關程序

用一個按鍵控制四個繼電器循環導通,程序由51單片機的程序改到STC15F104W上,第一次按鍵,第一個繼電器吸合,第二次按鍵,第二個繼電器吸合,第一個繼電器釋放,以此類推。最初的想法就是實現電風扇,三檔開關,程序裡面可以修改埠。#include<reg52.h>sbit key = P3^4; //定義按鍵void delayms(void);                         //延時void led_refresh(unsigned char

發表於 2020-12-16

相關焦點

  • 單片機編程用C語言還是彙編?
    單片機是一種可編程器件,單片機的出現使硬體設計變得更為簡單,產品的功能也更強大,而程序就是單片機的靈魂。目前功能稍微複雜一點的電子產品,都是以單片機為核心,再加以不通的外設電路實現不通的功能需求。單片機的編程可以通過彙編語言和C語言來實現。
  • 單片機基本結構及C語言編程基礎
    教科書的160頁給出了針對MCS51系列單片機的C語言擴展變量類型。單片機C語言編程基礎1、 十六進位表示字節0x5a:二進位為01011010B;0x6E為01101110。 2、 如果將一個16位二進數賦給一個8位的字節變量,則自動截斷為低8位,而丟掉高8位。 3、 ++var表示對變量var先增一;var—表示對變量後減一。
  • 單片機程序C語言與彙編語言混合編程
    中C語言與彙編語言混合編程本文引用地址:http://www.eepw.com.cn/article/201611/322505.htm1.C語言函數和彙編語言函數相互調用在這個示例中C語言函數main()調用彙編語言函數get_rand()以得到一個隨機數;彙編語言函數get_rand()首先調用C語言的標準庫函數rand()得到一個整型隨機值,然後用調用C語言函數mult()的方法把這個隨機值乘以main()函數傳遞給自己的實參,並把乘積值返回給
  • PIC單片機C語言編程教程(1)
    > 語言來開發單片機系統軟體最大的好處是編寫代碼效率高、軟體調試直觀、維護升級方便、代碼的重複利用率高、便於跨平臺的代碼移植等等,因此 C 語言編程在單片機系統設計中已得到越來越廣泛的運用。這幾款 Flash 型的單片機因其所具備的豐富的片上資源而最適用於單片機學習入門,因此筆者建議感興趣的讀者可從 PICC-Lite 入手掌握 PIC 單片機的 C 語言編程。
  • 【愛找茬】都是C語言,單片機C語言和普通的C語言究竟有什麼差異呢?
    C語言的設計目標是提供一種能以簡易的方式編譯、處理低級存儲器、產生少量的機器碼以及不需要任何運行環境支持便能運行的程式語言。 儘管C語言提供了許多低級處理的功能,但仍然保持著良好跨平臺的特性,以一個標準規格寫出的C語言程序可在許多電腦平臺上進行編譯,甚至包含一些嵌入式處理器(單片機或稱MCU)以及超級電腦等作業平臺。
  • 單片機提高C語言代碼效率的方法
    單片機的ROM和RAM的空間都很有限,當您編程時遇到單片機的ROM和RAM的不夠用的時候,或者您的程序要求較高的執行速度時,我們就得面對解決代碼效率問題了。如何提高代碼效率?現筆者以一個LED閃爍的程序為例與您探討。
  • 為什麼C語言是最適合單片機編程的高級語言!
    為什麼還在用C語言編程?答案是:C語言是最適合單片機編程的高級語言。 這個問題的意思應該是:現在有很多很好用的高級語言,如java,python等等,為什麼這些語言不能用來編寫單片機程序呢?那麼這個問題的答案就是:不是不能,而是不合適。
  • 單片機中C語言延時函數
    單片機C語言延時程序計算2009-11-02 22:15單片機C語言延時程序用C語言寫出來程序非常的簡練,它是一種模塊化的語言,一種比彙編更高級的語言,但是就是這樣一種語言也還是有它不足之處:它的延時很不好控制
  • 最適合單片機編程的高級語言,除了C語言,別無選擇!
    單片機為什麼還在用C語言編程?答案是:C語言是最適合單片機編程的高級語言。 這個問題的意思應該是:現在有很多很好用的高級語言,如java,python,VC等等,為什麼這些語言不能用來編寫單片機程序呢?那麼這個問題的答案就是:不能不能,而是不合適。
  • PIC單片機C語言程序實例
    編者按:為了幫助具有PIC單片機彙編語言知識的技術人員或工程師,快速掌握利用C語言編寫PIC單片機程序的方法,本刊特推出《PIC單片機C語言程序設計》系列連載文章。丈中給出的C語言程序實例,均是可執行的,讀者可以放心引用。      一、彙編語言與C語言      早期的單片機程序多採用彙編語言編寫。
  • 單片機C語言編程實現對舵機控制
    在智能小車編程過程中,舵機是實現小車避障、循跡的基礎。單片機通過控制舵機實現小車的轉向。本文主要介紹如何使用51單片機實現對舵機進行偏轉角度控制。所使用舵機型號為MG996R,使用晶片為STC89C52。
  • PIC單片機C語言程序設計(8)
    即可用MPLAB IDE7.40 對PIC 單片機建立彙編語言或C 語言的源程序、創建項目(project)、對源程序進行彙編(使用彙編語言時)或編譯(使用C 語言時),彙編或編譯通過後,會生成目標碼。hex 文件。有了目標碼。hex 文件,就可對PIC 單片機編程(燒寫)和對源程序進行模擬調試了。
  • 單片機C語言延時分析
    標準的C語言中沒有空語句。但在單片機的C語言編程中,經常需要用幾個空指令產生短延時的效果。這在彙編語言中很容易實現,寫幾個nop就行了。
  • 單片機c語言教程:建立你的第一個KeilC51項目
    KEIL uVISION2 是眾多單片機應用開發軟體中優秀的軟體之一,它支持眾多不一樣公司的MCS51架構的晶片,它集編輯,編譯,仿真等於一體,同時還支持,PLM,彙編和C語言的程序設計,它的界面和常用的微軟 VC++的界面相似,界面友好,易學易用,在調試程序,軟體仿真方面也有很強大的功能。本站提供的單片機c語言教程都是基於keilc51的。
  • PIC單片機asm與C混合編程
    在基於PICC 編譯環境下開發PIC 單片機的C 語言應用程式時基本無需關心其彙編編譯器,除非是在混合語言編程時用彙編語言編寫完整的彙編原程序模塊文件。其編譯選項設定的對話框見圖11-7,最重要的是優化使能控制項「Enable optimization」,一般情況下應該使用彙編器的優化以節約程序空間。
  • 淺談單片機c語言模塊化編程-附ds1302時鐘晶片驅動程序
    使用模塊化編程的時候,在.h裡聲明了數組。.c文件直接使用數組,在連接時會出現錯誤multiple public definition。在.h文件用extren聲明數組,在.c文件聲明賦值。錯誤解決。
  • 單片機C語言知識點全攻略(三)
    第八課、運算符和表達式(位運算符)  學過彙編的朋友都知道彙編對位的處理能力是很強的,但是單片機C語言也能對運算對象進行按位操作,從而使單片機C語言也能具有一定的對硬體直接進行操作的能力。位運算符的作用是按位對變量進行運算,但是並不改變參與運算的變量的值。如果要求按位改變變量的值,則要利用相應的賦值運算。
  • 51單片機C語言編程中對單片機絕對地址訪問的兩種方法
    在進行8051單片機應用系統程序設計時,編程都往往少不了要直接作業系統的各個存儲器地址空間。C51程序經過編譯之後產生的目標代碼具有浮動地址,其絕對地址必須經過BL51連接定位後才能確定。
  • 單片機c語言教程:C51變量
    這些存儲種類的具體含義和使用方法,將在第七課《變量的存儲》中進一步進行學習。而這裡的數據類型則是和我們在第四課中學習到的名種數據類型的定義是一樣的。說明了一個變量的數據類型後,還可選擇說明該變量的存儲器類型。存儲器類型的說明就是指定該變量在單片機c語言硬體系統中所使用的存儲區域,並在編譯時準確的定位。表6-1中是KEIL uVision2所能認別的存儲器類型。
  • 單片機C語言基礎編程源碼六則
    1.某單片機系統的P2口接一數模轉換器DAC0832輸出模擬量,現在要求從DAC0832輸出連續的三角波,實現的方法是從P2口連續輸出按照三角波變化的數值,從0開始逐漸增大,到某一最大值後逐漸減小,直到0,然後再從0逐漸增大,一直這樣輸出。試編寫一函數,使從P2口輸出的值產生三角波,並且使三角波的周期和最大值通過入口參數能夠改變。