24小時聯系電話:18217114652、13661815404
中文
行業資訊
嵌入式固件開發簡介
嵌入式系統是一個獨立的智能系統,專用于從電源啟動時開始運行一組任務或應用程序。
這與在臺式機或類似設備上啟動應用程序的方式形成對比,在臺式機或類似設備上,用戶不必專門加載任何內容。
嵌入式系統的一個例子是家用洗衣機。一旦選擇并開始正確的洗滌周期,它將運行直到完成。
智能部分是根據用戶的選擇確定水位,執行清洗,漂洗和旋轉周期以及其他相關任務。
這演示了典型嵌入式系統的幾個方面。洗衣機必須接收并響應用戶的選擇,感測水位,并為每種操作模式確定合適的運行時間。洗衣機還需要控制斷水閥和電動機。
大多數嵌入式系統在所有這些操作的中心都包含一個微控制器。該微控制器是單個硅芯片,可以對其進行編程以執行您的應用程序所需的所有操作。
在我們的洗衣機示例中,編程將由某人以嵌入式編程語言編寫,并在制造過程中下載到微控制器中。
讓我們仔細看看這些類型的嵌入式系統。還包括一個簡單的C語言嵌入式程序,它是編寫嵌入式應用程序的最流行語言之一。
在嵌入式編程中,C語言以及程度較小的C ++被廣泛使用。這樣做的原因是,除了匯編語言外,可以說C仍然是最接近硬件的語言。
雖然匯編語言更接近,但它非常特定于實際的基礎硬件,并且隨微控制器體系結構的不同而不同。另一方面,C更加標準化,同時仍提供對底層硬件的足夠控制。
為了能夠遵循該代碼示例,我假設讀者已經對C以外的編程語言有所了解。因此,在該代碼示例中,我不會花時間在變量,循環,條件語句或功能,至少不涉及其背后的概念。
什么是微控制器?
微控制器的核心是中央處理器或CPU,它與臺式機或筆記本電腦中的中央處理器沒有什么不同,只不過它的功能通常較弱。
該CPU運行原始人類程序員編寫的指令集或程序。最接近CPU的是一些寄存器。這些是臨時存儲單元,具有非常快的訪問時間,與CPU本身的訪問時間相匹配。
這些寄存器具有CPU正常運行所需的許多功能。有一個程序計數器,有時也稱為指令指針,它包含CPU將執行的下一條指令的地址。
有一個堆棧指針寄存器,用于訪問稱為堆棧的特殊內存區域,稍后再進行介紹。有一個標志寄存器,用于保存某些CPU操作的結果狀態,例如算術運算的正或負結果。
然后是通用寄存器,用于保存CPU在其上進行的操作以及這些操作的結果。
除了CPU寄存器外,CPU還連接到不同的外圍設備,例如IO端口,中斷控制器,計時器,USARTS,SPI,I 2 C,以及在更高級的微控制器中的視頻輸入或輸出外圍設備和內存管理單元。這些外圍設備的更多信息將在以后介紹。
此外,CPU可以訪問閃存,RAM和EEPROM存儲器。所有這些都集成到單個芯片或集成電路中。具有所有這些集成外設和存儲器的單芯片就是微控制器。
相比之下,微處理器基本上只是一個功能非常強大的CPU,它的寄存器以及某些高級外圍設備都位于芯片中。所有其他外圍設備都是微處理器外部的獨立芯片。當然,與單個微控制器芯片相比,它們更強大,并且具有更多功能。
例如,臺式計算機可以具有16 MB或更多的內存,而微控制器則可以低至2 KB,是8000X的兩倍。
如前所述,嵌入式應用程序只是在被調用時執行的一組任務。以我們的普通洗衣機為例。
圖1 –洗衣機是嵌入式系統的常見示例。
對于給定的洗滌周期,洗衣機接受用戶的選擇,該過程控制進水閥,洗滌和漂洗周期的時間段和溫度,排水用過的水的水泵等。
另外,為用戶顯示的每個操作的狀態或剩余時間。所有這些任務都由洗衣機內置的微控制器控制。
繼續該示例,控制器重復執行三個步驟:
接收輸入,例如開始按鈕,計時器倒數,水位等。
處理輸入,決定執行什么動作以及何時執行它們。
根據步驟2決定的動作進行操作,并控制一些輸出,例如水泵,斷水和進水閥,數字顯示等。
當然,針對不同的應用程序所采取的實際操作將有所不同。在此示例中,步驟2是由運行一組預編程指令的CPU執行的。步驟1和3由CPU控制的外圍設備執行。接下來將描述其中一些。
微控制器存儲器和外設
在進入適當的外圍設備之前,首先需要描述微控制器的存儲系統。所有微控制器至少具有兩種類型的存儲器:閃存和SRAM。
閃存是存儲用戶編寫的程序的位置。就像臺式機中的傳統硬盤一樣,閃存是非易失性的,用于存儲CPU將要執行的程序。
但是,寫入閃存的速度相對較慢,這就是使用SRAM存儲器的原因。可以非常快速地對其進行訪問(寫入或讀取)。但是,它是易失性的,因此如果斷開微控制器的電源,則會丟失其內容。
SRAM通常分為三個區域:用于存儲變量的常規區域,堆和堆棧。堆是可以由正在運行的程序按需按塊訪問的內存區域,然后在不再需要時返回,從而可以由正在運行的程序的另一部分再次請求它。
堆棧是SRAM的一個特殊部分,用于嵌套函數調用,所有程序的構造塊以及將參數傳遞給所述函數。
一些微控制器還具有EEPROM,它也是與閃存分開的非易失性存儲器類型,通常用于存儲用戶設置或校準值。某些微控制器實際上可以使用閃存的一部分來做到這一點。
轉到實際的外圍設備,要注意的一件事是它們可以在許多模式和配置下運行。要選擇各種模式,首先要做的是閱讀數據表。
所有外設都有配置寄存器,其中有幾個。它們與CPU寄存器不同,并且每個外設都有其自己的集合,可以對其進行編程以使外設以某種方式運行。
由于用戶無法直接訪問外設寄存器,因此對其進行編程的方法是實際上讓CPU運行一些設置代碼,然后將適當的值寫入所選外設的寄存器中。以下是一些常見外圍設備的簡要說明:
GPIO –可以將這些通用輸入輸出外設編程為邏輯電平輸入或輸出。
計時器–可以對它們進行編程以提供精確的時序,并可以輸出定時脈沖或連續脈沖序列,或者可以測量兩個脈沖沿之間的時間間隔。
美國ARTS –這些用于兩個設備之間的雙向串行通信,在該設備上逐位發送或接收數據。
I 2 C –這是許多模塊(例如傳感器和顯示器)使用的接口。同一條通信總線上可以有許多這樣的設備。每個都可以單獨解決。
SPI –這是另一個具有相似功能的接口I 2 C,但速度要快得多。I 2 C或SPI的選擇通常取決于特定模塊的用途。
圖2顯示了典型的8位微控制器的內部模塊。關于該框圖要注意的一件事是,它表明只有GPIO端口(此處顯示為端口B,端口C和端口D)連接到微控制器的外部可用引腳。
在實際的微控制器中,這些引腳在各種外設之間共享,因為大多數微控制器的外部可用引腳數量有限。例如,一個引腳可以設置為GPIO引腳或USART引腳,但不能同時設置為兩者。
圖2 –典型的8位微控制器中的主要模塊
開發微控制器的應用程序固件
應用軟件開發通常在交叉開發平臺上完成,例如Windows PC,Linux box或Mac。
一般過程是使用集成語言(如C)在集成開發環境(IDE)中編寫代碼,使用庫將代碼模塊編譯和鏈接,并使用庫,然后將二進制文件下載到微控制器以進行測試和調試。
這通常是一個迭代過程。為了擴展剛剛描述的過程,IDE只是提供了一個方便的,多合一的平臺,在該平臺中,實際輸入源代碼,編譯,鏈接和加載的過程可以在一個地方完成。
編譯和鏈接需要編譯器/鏈接器,該編譯器/鏈接器可以生成適合于目標微控制器的二進制代碼。
加載可以通過幾種方式完成。一種是讓外部設備編程器插入目標微控制器以加載已編譯的二進制文件。然后將已編程的微控制器插入其預期的硬件模塊中進行測試。
另一種方法是在硬件板上構建編程接口,并對已經連接到其硬件的微控制器進行編程。此方法通常稱為系統內編程或ISP。這通常稱為“系統編程”。
對于某些微控制器,另一種方法是通過二進制文件的外設之一(通常是USART)將二進制文件下載到微控制器中。
為此,微控制器必須正在運行一個稱為引導加載程序的預加載程序,該程序會接收新程序,然后進行自我更新。
由于引導加載程序本身從未被擦除,因此這意味著微控制器僅需使用引導加載程序代碼在外部進行一次編程。
典型的微控制器固件布局:
// This is a single line comment in C
/*
Multi-line comments are braced with slash-asterisk and asterisk-slash as shown here.
Still a comment line.
*/
// Include header files
// These files contain function prototypes for functions used in this, or other source modules, as well as libraries.
// Some also contain constants and other pre-processor directives, or macros. C has very powerful macro processing capabilities.
// Here is a simple example based on using an AVR microcontroller such as is used in many Arduino boards such as the Uno.
#include <avr/io.h>
// In C, the program always start with the main() function, regardless of where it is located in the source module. Some
// prefer it to be at the top of the file; others place the main() function as the very last function in the file.
// In embedded applications, main() usually does not have any arguments since there is no command-line arguments to pass to the program at the start.
main() // Can also use void main(void) to emphasize that this main() function takes, and returns, no arguments.
{
// Put peripheral initialization code here, or call functions to do so.
// An embedded application is basically one that runs forever.
while(1)
{
// Application code goes here.
}
}
下面是一個實際的工作示例,同樣基于AVR微控制器。例如,它可以用于使連接到AVR ATMEGA 328 PORTB的GPIO5的LED閃爍。
Atmel Studio is a free and very suitable IDE plus compiler/linker that can be used for AVR microcontrollers.
// The io.h header file provides names matching those used in the specifications for the peripheral registers.
#include <avr/io.h>
// This one contains function prototypes for the delay functions defined in the avr-libc library.
#include <util/delay.h>
// Function prototypes must be declared if the functions are called before their actual definitions, as is the case here.
// Usually, these are put in a header file that is included in the source module, but they are actually declared here to show how this is done.
void setup(void);
void loop(void);
// Starting point for the program.
void main(void)
{
setup();
while(1)
{
loop();
}
}
// Actual function setup()
void setup(void)
{
// DDRB is the Data Direction Register of Port B, which is 8 bits wide. Each bit corresponds to an actual pin on the microcontroller.
// Setting a bit of this register to 1 means this corresponding pin is an output.
DDRB |= (1 << PB5); // Same DDRB = DDRB | 0b0010000. This sets GPIO5 of PortB as a digital output, and leaves the other GPIO’s untouched.
}
// Actual function loop()
void loop(void)
{
PORTB |= (1 << PB5); // Set PB5 pin of PORTB to 1
_delay_ms(1000); // 1000ms delay
PORTB &= ~(1 << PB5); // Set PB5 pin of PORTB to 0
_delay_ms(1000);
}
最后,對于那些擁有Arduino Uno的用戶,該程序實際上將編譯并閃爍板上LED,因為Arduino Uno對AVR微控制器使用了相同的GCC編譯器。
只需加載一個空白草圖。然后,在空白草圖的設置功能中的功能setup()的花括號之間復制代碼。
同樣,將功能loop()的花括號之間的代碼復制到草圖的循環功能中。編譯并運行。LED將開始閃爍。