Efficient C Code for 8-bit Microcontrollers
為8位微控制器寫高效率的C程式
Submitted by webmaster on 2 December, 2007 - 01:53.
by Nigel Jones由尼格爾瓊斯


 


譯者注:
0.中文化:CHANNING
1.翻譯的很爛 加減看吧
2.下面這些前言都是在講ANSI C的東西可以省略 直接從Data types開始看就可以了


 


The 8051, 68HC11, and Microchip PIC are popular microcontrollers, but they aren’t necessarily easy to program.
8051 , 68HC11 ,和Microchip PIC 是受歡迎的微控制器,卻不容易做程式設計。


This article shows how the use of ANSI C and compiler-specific constructs can help generate tighter code.
本文介紹如何使用的ANSI C和編譯器的具體結構可以幫助產生更緊密的程式碼。


Getting the best possible performance out of the C compiler for an 8-bit microcontroller isn’t always easy.
獲得最佳性能的C編譯器的8位微控制器總不是很容易。


This article concentrates mainly on those microcontrollers that were never designed to support high-level languages, such as members of the 8051, 6800 (including the 68HC11), and Microchip PIC families of microcontrollers.
Newer 8-bit machines such as the Philips 8051XA and the Atmel Atmega series were designed explicitly to support high-level languages and, as such, may not need all the techniques I describe here.
本文主要集中在這些沒有設計去支援高階語言微控制器,如成員8051 , 6800 (包括68HC11 ) ,和Microchip的微控制器 PIC 系列。
較新的8位機,如飛利浦8051XA和在Atmel公司Atmega 這一系列是明確被設計去支持高階語言,因此,可能不需要我所描述的技術。


My emphasis is not on algorithm design, nor does it depend on a specific microcontroller or compiler.
我的重點不是演算法設計,也不依賴於特定的微控制器或編譯器。


Rather, I describe general techniques that are widely applicable.
相反,我描述的一般技術,廣泛適用。


In many cases, these techniques work on larger machines, although you may then decide that the trade-offs involved aren’t worthwhile.
在許多情況下,這些技術工作在較大的機器,但你可以決定取捨是不值得使用。


Before jumping into the meat of the article, let’s briefly digress with a discussion of the philosophy involved.
在跳到本文前,讓我們簡要的離題去討論一些複雜的問題。


The microcontrollers I've named are popular for reasons of size, price, power consumption, peripheral mix, and so on.
微控制器受歡迎的原因是體積,價格,功耗,週邊組合,等等。


Notice that “ease of programming” is conspicuously missing from this list.
請注意, “易於程式設計”顯然的不在這個列表內。


Traditionally, these microcontrollers have been programmed in assembly language.
傳統上,這些微控制器都被列為使用組合語言去設計。


In the last few years, many vendors have recognized the desire of users to increase their productivity, and have introduced C compilers for these machines—many of which are extremely good.
在過去的幾年裡,許多供應商都承認 客戶們都希望提高生產率,並介紹了這些機器的C編譯器,其中有許多是非常好的。


However, it’s important to remember that no matter how good the compiler, the underlying hardware has severe limitations.
然而,重要的是要記住,無論多麼優秀的編譯器,底層的硬體有嚴重的局限性。


Thus, to write efficient C for these targets, it’s essential that we be aware of what the compiler can do easily and what requires compiler heroics.
因此,目標就是寫有效率C,這是我們必須知道什麼可以做,編譯器可以很容易,哪些需要編譯高手去編譯。


In presenting these techniques, I have taken the attitude that I wish to solve a problem by programming a microcontroller, and that the C compiler is a tool, no different from an oscilloscope.
In other words, C is a means to an end, and not an end in itself.
As a result, many of my comments will seem heretical to the high-level language purists out there.
在介紹這些技術,我採取的態度是我想解決微控制器編程的問題,而C編譯器是一個工具,跟示波器沒有什麼不同。
換句話說, C是達到目的的一種手段,並本身不是目的。
因此,我的許多意見將是被視為高階語言的邪說。


 


ANSI C的ANSI C
The first step to writing a realistic C program for an 8-bit computer is to dispense with the concept of writing 100% ANSI code.
This concession is necessary because I don’t believe it’s possible, or even desirable, to write 100% ANSI code for any embedded system, particularly for 8-bit systems.
第一步,去寫一個8位元微控制器實際可行的C程序且省略寫100% ANSI程式碼。
這項讓步是必要的,因為我不相信這是可能的,甚至是可取的,寫100% ANSI程式碼到任何嵌入式系統,尤其是對8位系統。


Some characteristics of 8-bit systems that prevent ANSI compliance are:
在一些有特色的8位系統,防止ANSI遵守標準是:


Embedded software interacts with hardware, yet ANSI C provides extremely crude tools for addressing registers at fixed memory locations
嵌入式軟件與硬體交互的,但ANSI C提供的原始的工具是定址在固定的記憶體位址
All nontrivial systems use interrupts, yet ANSI C doesn’t have a standard way of coding interrupt service routines
所有平凡系統都有使用中斷,但ANSI C沒有一個標準去識別中斷服務常式
ANSI C has various type promotion rules that are absolute performance killers on an 8-bit computer
ANSI C有不同的更新規則,是完全的限制的8位元微控制器的效能
Many older microcontrollers feature multiple memory banks, which have to be hardware swapped in order to correctly address the desired variable
許多較老的微控制器特色是多重記憶體存儲區,必須硬體去切換正確的位址,以便正確處理所需的變數
Many microcontrollers have no hardware support for C's stack (ie, they lack a stack pointer)
許多微控制器沒有硬體支持C的堆疊(即,他們缺乏一個堆疊指標)
This is not to say that I advocate junking the entire ANSI C standard. I take the view that one should use standard C as much as possible.
這並不是說,我主張丟棄整個的ANSI C標準。我認為是應可能的使用標準的C。


However, when it interferes with solving the problem at hand, do not hesitate to bypass it.
然而,當與手邊的問題解決方法有所衝突時,,請不要猶豫,繞過它。
Does this interfere with making code portable and reusable?
這是否會干擾程程式碼的可攜性和可重複使用?
Absolutely.
當然會。
But portable, reusable code that doesn’t get the job done isn’t much use.
但是,可攜性和可重複使用的程式碼,不完成任務是沒有多大用處。


I’ve also noticed that every compiler has a switch that strictly enforces ANSI C and disables all compiler extensions.
I suspect that this is done purely so that a vendor can claim ANSI compliance, even though this feature is practically useless.
I have also observed that vendors who strongly emphasize their ANSI compliance often produce inferior code (perhaps because the compiler has a generic front end that is shared among multiple targets) when compared to vendors that emphasize their performance and language extensions.
我也注意到,每一個編譯器都可切換是否嚴格執行的ANSI C編譯器,並禁用所有的擴展。
我懷疑這是純粹的使供應商可以索取ANSI的承諾,即使此功能實際上是無用的。
我也觀察供應商極力強調遵守其標準往往產生劣程式碼(也許是因為編譯器有一個通用的前端之間共享的多目標)的供應商相比,強調他們的表現和語言的擴展。


Enough about the ANSI standard.
有關ANSI標準已經足夠了。
Let’s now discuss specific actions that can be taken to make your code run efficiently on an 8-bit microcontroller.
我們現在討論的具體行動可以採取的使您的程式碼執行高效能的8位微控制器。
The most important, by far, is the choice of data types.
最重要的是,到目前為止,是選擇的資料型別。


Data types
資料型別
Knowledge of the size of the underlying data types, together with careful data type selection, is essential for writing efficient code on eight-bit machines.
知道基本資料型別的大小,及認真選擇資料型別,對寫高效能的8位元微控制器程式碼是必要的。
Furthermore, understanding how the compiler handles expressions involving your data types can make a considerable difference in your coding decisions.
此外,了解編譯器處理你程式裡的資料型別不同的表達 可以作出很大的差異。
These topics are discussed in the following paragraphs.
這些議題是討論在下面的段落中。


Data type size
資料型別的大小
In the embedded world, knowing the underlying representation of the various data types is usually essential.
在嵌入式世界,了解基本代表了各種資料型別通常是必不可少的。
I have seen many discussions on this topic, none of which has been particularly satisfactory or portable.
我看到了許多討論這個問題,其中沒有一個是特別令人滿意的或便利的。
My preferred solution is to include a file, <types.h>, an excerpt from which appears below:
我的首選的解決方案將包括一個文件, <types.h> ,其中的摘錄如下:


 


#ifndef TYPES_H
#define TYPES_H
#include <limits.h>


/* Assign a compiler-specific data type to BOOLEAN */
/ *指定一個編譯器的資料型別為布林值* /
#ifdef _C51_
typedef bit BOOLEAN
#define FALSE 0
#define TRUE 1
#else
typedef enum {FALSE=0, TRUE=1} BOOLEAN;
#endif


/* Assign an 8-bit signed type to CHAR */
/ *指定一個8位有號類型字元* /
#if (SCHAR_MAX == 127)
typedef char CHAR;
#elif (SCHAR_MAX == 255)


/* Implies that by default chars are unsigned */
/ *意味著默認字元是有號字元* /
typedef signed char CHAR;
#else


/* No eight bit data types */
/ *沒有8位的資料型別* /
#error Warning! Intrinsic data type char is not eight bits
#endif


/* Rest of the file goes here */
/ *休息的檔案這裡* /
#endif


The concept is quite simple.
這個概念很簡單。
The file types.h includes the ANSI-required file limits.h .
該文件types.h包括標準要求的文件limits.h 。
It then explicitly tests each of the predefined data types for the smallest type that matches signed and unsigned 1-, 8-, 16-, and 32-bit variables.
然後,明確地測試每一個預定義的資料型別的最小的類型相匹配有號數和無號數的1 - , 8 - , 16 - ,和32位變數。
The result is that my data type UCHAR is guaranteed to be an 8-bit unsigned variable, INT is guaranteed to be a 16-bit signed variable, and so forth.
In this manner, the following data types are defined: BOOLEAN, CHAR, UCHAR, INT, UINT, LONG, and ULONG.
其結果是,我的資料型別UCHAR保證一個8位無號數變數, int是保證16位有號數變數,等等。
通過這種方式,下面的資料型別的定義:BOOLEAN, CHAR, UCHAR, INT, UINT, LONG, and ULONG 。


Several points are worth making:
有幾個要點值得決策:


The definition of the BOOLEAN data type is difficult.
去定義,布林資料型別是很困難的。
Many 8-bit processors directly support single-bit data types, and I wish to take advantage of this if possible.
許多8位微控制器直接支持單BIT的資料型別,我想利用這個可能的話。
Unfortunately, since ANSI is silent on this topic, it’s necessary to use compiler-specific code
不幸的是,關於這一主題由於標準是無定義的,所以必須使用編譯器特定的程式碼
Some compilers define a char as an unsigned quantity, such that if a signed 8-bit variable is required, one has to use the unusual declaration signed char
有些編譯器定義一個字元作為一個無號數的數量,例如,如果一個有號數8位變數需要,必須使用不同尋常的宣告他是有號數字元
Note the use of the #error directive to force a compile error if I can’t achieve my goal of having unambiguous definitions of BOOLEAN, UCHAR, CHAR, UINT, INT, ULONG, and LONG
注意:如果我不能達到我的目標有明確的定義BOOLEAN, UCHAR, CHAR, UINT, INT, ULONG, and LONG 就使用#error指令迫使編譯錯誤吧
In all of the following examples, the types BOOLEAN, UCHAR, and so on will be used to specify unambiguously the size of the variable being used.
在所有從下面的例子中,BOOLEAN, UCHAR等將被用來指定明確的規模變數被使用。


Data type selection
資料型別選擇
There are two basic guidelines for data type selection on 8-bit processors:
有兩個基本準則的資料型別選擇的8位微控制器:


Use the smallest possible type to get the job done
盡可能用較小的型別去完成任務
Use an unsigned type whenever possible
盡可能使用無號數類型


The reasons for this are simply that many 8-bit processors have no direct support for manipulating anything more complicated than an unsigned 8-bit value.
其中的原因很簡單,許多8位微控制器並沒有直接支持超過8BIT的運算都是用複雜的程式段代替16位與32位的運算。
However, unlike large machines, eight-bitters often provide direct support for manipulation of bits.
然而,與大機器, 8BIT往往提供直接支持操縱位運算。
Thus, the fastest integer types to use on an 8-bit CPU are BOOLEAN and UCHAR.
因此,最快的整數類型上使用一個8位的CPU是BOOLEAN and UCHAR 。
Consider the typical C code:
考慮到典型的C程式碼:


int is_positive(int a)
{
(a>=0) ? return(1) : return (0);
}


The better implementation is:
更好地執行是:


BOOLEAN is_positive(int a)
{
(a>=0) ? return(TRUE) : return (FALSE);
}



On an 8-bit processor we can get a large performance boost by using the BOOLEAN return type because the compiler need only return a bit (typically via the carry flag), vs. a 16-bit value stored in registers.
在一個8位微控制器,我們可以得到一個大的性能提升使用布林返回類型,因為編譯器只需要返回一個位(通常是通過進位旗標) ,與一個16位值儲存在暫存器。
The code is also more readable.
該守則也更具可讀性。


Let’s take a look at a second example.
讓我們來看看在第二個例子。
Consider the following code fragment that is littered throughout most C programs:
考慮下面的程式碼片段是散落的大部分C程序:


int j;


for (j = 0; j < 10; j++)
{
...
}


This fragment produces horribly inefficient code on an 8051. A better way to code this for 8-bit CPUs is as follows:
對一個8051這片段產生可怕的低效率的程式碼 。在8位的CPU上 一種更好的程式碼如下:


UCHAR j;


for (j = 0; j < 10; j++)
{
...
}


The result is a huge boost in performance because we are now using an 8-bit unsigned variable (that can be manipulated directly) vs. a signed 16-bit quantity that will typically be handled by a library call.
其結果是一個巨大的性能提升,因為我們現在正在使用的8位無號數變數(可以直接操縱)對有號數的16位數量,通常由一個函式庫呼籲。
Note also that there is generally no penalty for coding this way on most big CPUs (with the exception of some RISC processors).
在大部分的CPU這種編碼是沒有副作用(除了一些RISC微控制器) 。
Furthermore, a strong case exists for doing this on all machines.
此外,強大的情況下存在這樣做的所有機器。
Those of you who know Pascal are aware that when declaring an integer variable, it’s possible, and normally desirable, to specify the allowable range that the integer can take on.
你們知道Pascal可以當宣布一個整數變數,可以指定允許範圍內的整數。


For example:


type loopindex = 0..9;
var j loopindex;


Upon rereading the code later, you’ll have additional information concerning the intended use of the variable.
重讀時的程式碼後,您有其他有關打算使用的變數。
For our classical C code above, the variable int j may take on values of at least –32768 to +32767.
我們的經典的C程式碼段,變數可能就值至少-32768到32767 。
For the case in which we have UCHAR j, we inform others that this variable is intended to have strictly positive values over a restricted range.
對於範例中,我們已經定義 UCHAR j ,我們告知他人,這種變數的目的是無號數的值。
Thus, this simple change manages to combine tighter code with improved maintainability—not a bad combination.
因此,這個簡單的變化更嚴格的管理相結合的程式碼,改善可維護性,不是一個糟糕的組合。


Enumerated types
列舉類型
The use of enumerated data types was a welcome addition to ANSI C.
ANSI C受歡迎的型別為使用列舉的資料型別
Unfortunately, the ANSI standard calls for the underlying data type of an enum to be an int.
不幸的是, ANSI標準要求的基本資料型別的列舉是一個int 。
Thus, on many compilers, declaration of an enumerated type forces the compiler to generate 16-bit signed code, which, as I’ve mentioned, is extremely inefficient on an 8-bit CPU.
This is unfortunate, especially as I have never seen an enumerated type list go over a few dozen elements;
it could usually easily be fit in a UCHAR.
因此,許多編譯器,宣告列舉類型編譯器產生的16位有號數程式碼,因為我已經提到的,是非常低效的8位CPU 。
這是不幸的,尤其是我從來沒有見過列舉有超過幾十個元素,
它可以很容易被通常適合在UCHAR 。
To overcome this limitation, several options exist, none of which is palatable:
為了克服這一限制,存在著幾種選擇,其中沒有一個是好用的:


Check your compiler documentation, which may show you how to specify via a (compiler-specific) command line switch that enumerated types be put into the smallest possible data type
檢查您的編譯器文件,這可能告知您如何指定通過(編譯器特定的)的命令行開關,使列舉類型盡可能縮小資料型別
Accept the inefficiency as an acceptable trade-off for readability
接受一個無效率的 或 一個可接受的犧牲一些可讀性
Dispense with enumerated types and resort to lists of manifest constants
去免除列舉類型限制



Integer promotion
整數提升
The integer promotion rules of ANSI C are probably the most heinous crime committed against those of us who labor in the 8-bit world.
ANSI C的整數提升規則可能是對8BIT世界 最令人髮指的罪行。
I have no doubt that the standard is quite detailed in this area.
我毫無疑問,這一標準是相當詳細的這方面的工作。
However, the two most important rules in practice are the following:
但是,兩個最重要的規則在實踐中如下:


Any expression involving integral types smaller than an int have all the variables automatically promoted to int
任何涉及小於一個int變數運算 都自動晉升為int
Any function call that passes an integral type smaller than an int automatically promotes the variable to an int, if the function is not prototyped
任何函數呼叫傳遞一個不可分割的類型小於一個int自動推動一個int變數,如果沒有的功能原型
The key word here is automatically .
這裡的關鍵是這個規則是自動的 。
Unless you take explicit steps, the compiler is unlikely to do what you want.
除非你採取明確的步驟,編譯器是不可能做到你想要的。
Consider the following code fragment:
考慮下面的程式碼片段:


CHAR a,b,res;
...
res = a+b;


The compiler will promote a and b to integers, perform a 16-bit addition, and then assign the lower eight bits of the result to res.
編譯器將提升A和B整數,執行16位運算,然後分配較低的8位的結果去儲存。
Several ways around this problem exist.
幾種方式解決這個問題存在。
First, many compiler vendors have seen the light, and allow you to disable the ANSI automatic integer promotion rules.
首先,許多編譯器廠商都看到了這個問題,並允許您禁用的ANSI自動整數提升規則。
However, you’re then stuck with compiler-dependant code.
然而,你再堅持編譯依賴程式碼。


Alternatively, you can resort to very clumsy casting, and hope that the compiler’s optimizer works out what you really want to do.
另外,您也可以訴諸十分笨拙鑄造,並希望該編譯器的最佳化工程什麼你真的想要做的事。
The extent of the casting required seems to vary among compiler vendors.
的程度,鑄造需要似乎有所不同的編譯器供應商。
As a result, I tend to go overboard:
因此,我傾向去強制定型:


res = (CHAR)((CHAR)a + (CHAR)b);


With complex expressions, the result can be hideous.
複雜的運算,結果可能是可怕的。



More integer promotion rules
更多整數提升規則
A third integer promotion rule that is often overlooked concerns expressions that contain both signed and unsigned integers.
第三個整數提升規則,常常被忽視的關切表達同時包含有號數和無號數的整數。
In this case, signed integers are promoted to unsigned integers. Although this makes sense, it can present problems in our 8-bit environment, where the unsigned integer rules.
在這種情況下,有號數整數是晉升為無號數整數。雖然這是有道理的,它可以存在的問題在我們的8位環境下,規則的無號數整數。


For example:


void demo(void)
{
UINT a = 6;
INT b = -20;


(a+b > 6) ?
puts(“More than 6”) :
puts(“Less than or equal to 6”);
}


If you run this program, you may be surprised to find that the output is “More than 6.”
This problem is a very subtle one, and is even more difficult to detect when you use enumerated data types or other defined data types that evaluate to a signed integer data type.
如果您執行此程序,您可能會驚訝地發現,其輸出為“More than 6.”
這個問題是一個非常微妙的,更是難以察覺當您使用所列舉的資料型別或其他定義的資料型別,評估一個符號整數資料型別。
Using the result of a function call in an expression is also problematic.
使用的結果,函數呼叫在一個表達式中也存在問題。


The good news is that in the embedded world, the percentage of integral data types that must be signed is quite low, thus the potential number of expressions in which mixed types occur is also low.
好消息是,在嵌入式世界的百分比組成的資料型別, 必須有號數相當低,因此,潛在的一些表現形式,其中混合型發生也很低。
The time to be cautious is when reusing code that was written by someone who didn’t believe in unsigned data types.
這時要格外小心是 重新使用一些不相信無號數型別的程式設計師寫的程式碼時。


Floating-point types
浮點類型
Floating-point arithmetic is required in many applications.
However, since we’re normally dealing with real-world data whose representation rarely goes beyond 16 bits (a 20-bit A/D converter on an 8-bit machine is rare), the requirements for double-precision arithmetic are tenuous, except in the strangest of circumstances.
浮點算法需要在許多應用中。
然而,由於我們通常處理真實世界的數據,其代表性很少超出16位( 20位A / D轉換器的8位機是非常罕見) ,在要求雙精度算術是脆弱的,除非在最奇怪的情況。


Again, the ANSI people have handicapped us by requiring that any floating-point expression be promoted to double before execution.
Fortunately, a lot of compiler vendors have done the sensible thing, and simply defined doubles to be the same as floats, so that this promotion is benign.
同樣,ANSI 的傷殘人士要求任何浮點表達晉升為雙精度去執行。
幸運的是,大量的編譯器廠商都做了明智的事,只可以簡單的定義雙精度跟浮點一樣,使這一提升是良性的。
Be warned, however, that many reputable vendors have made a virtue out of providing a genuine double-precision data type.
被警告,但是,許多著名廠商都提供了一個真正的雙精度資料型別。
The result is that unless you take great care, you may end up computing values with ridiculous levels of precision, and paying the price computationally.
其結果是,除非你採取非常謹慎,否則你可能最終計算值與荒謬的精度,並為此付出代價計算。
If you’re considering a compiler that offers double-precision math, study the documentation carefully to ensure that there is some way of disabling the automatic promotion of float to dobuble.
如果您正考慮編譯器,提供雙精度數學,仔細研究文件,以確保有一定的方式禁用自動促進浮到dobuble 。
If there isn’t, look for another compiler.
如果沒有,尋找另一個編譯器。



While we’re on this topic, I’d like to air a pet peeve of mine.
雖然我們對這個議題,我像是對著空氣生氣的寵物。
Years ago, before decent compiler support for 8-bit processors was available, I would code in assembly language using a bespoke floating-point library.
幾年前,在編譯器支持8位微控制器的情況下,我將程式碼彙編語言使用的是定制浮點函式庫。
This library was always implemented using 24-bit floats, with a long float consuming four bytes. I found that this was more than adequate for the real world.
這各函式庫一直是使用24位的浮點,長浮點花費四個位元組。我發現,在現實世界這是足夠有餘的。
I’ve yet to find a compiler vendor that offers this as an option.
我還沒有找到一個編譯器供應商有提供,這是一種選擇。
My guess is that the marketing people insisted on a true ANSI floating-point library, the real world be damned.
我的猜測是,市場人員堅持一個真正的ANSI浮點函式庫,在現實世界是該死的。
As a result, I can calculate hyperbolic sines on my 68HC11, but I can’t get the performance boost that comes from using just a 24-bit float.
因此,我可以在68HC11計算雙曲線 ,但我不能得到的性能提升,因為僅使用了24位浮點。


Having moaned about the ANSI-induced problems, let’s turn to an area in which ANSI has helped a lot.
在呻吟的標準引起的問題,讓我們談談在這方面的ANSI幫助很大。
Im referring to the keywords const and volatile, which, together with static, allow the production of better code.
我指的是 關鍵字 const 和volatile,這與靜態的,允許產生更好的程式碼。



C's static keyword
C的static關鍵字
The keywords static, volatile, and const together allow one to write not only better code (in the sense of information hiding and so forth) but also tighter code.
關鍵字static,volatile,const 允許一起寫 這不僅是更好的程式碼(意義上的信息隱藏等等) ,而且還嚴格的程式碼。


Static variables
靜態變數
When applied to variables, static has two primary functions. The first and most common use is to declare a variable that doesn’t disappear between successive invocations of a function.
當適用於變數,static的有兩個主要功能。第一和最常見的使用是要宣告一個變數,不會消失,可連續使用於函式的呼叫。


For example::


void func(void)
{
static UCHAR state = 0;
switch (state) { ... }
}


In this case, the use of static is mandatory for the code to work.
在這種情況下,使用static是程式碼工作的代理者。


The second use of static is to limit the scope of a variable. A variable that is declared static at the module level is accessible by all functions in the module, but by no one else.
第二個使用靜態是限制變數的範圍。一個變數在函數本體被宣告成靜態變數則在這一函數被呼叫過程中維持其值不變。
This is important because it allows us to gain all the performance benefits of global variables, while severely limiting the well-known problems of globals.
這很重要,因為它使我們能夠獲得的所有性能優勢的全域變數,而嚴重限制了眾所周知的全域問題。
As a result, if I have a data structure which must be accessed frequently by a number of functions, I’ll put all of the functions into the same module and declare the structure static.
因此,如果我有一個資料結構,必須經常使用的一些功能,我把所有的功能集成到同一函數,並宣布靜態結構。
Then all of the functions that need to can access the data without going through the overhead of an access function, while at the same time, code that has no business knowing about the data structure is prevented from accessing it.
那麼所有的功能,需要能存取數據而不必通過間接的接入功能,同時,程式碼,沒有人看到資料結構所以無法存取它。
This technique is an admission that directly accessible variables are essential to gaining adequate performance on small machines.
這種技術是一種承認,直接進入變數是必不可少的,那在小機器也能獲得足夠的性能。


A few other potential benefits can result from declaring module level variables static (as opposed to leaving them global).
其他一些潛在的好處可能造成宣布函數級變數靜態的(而不是讓他們的全域) 。
Static variables, by definition, may only be accessed by a specific set of functions.
靜態變數,根據定義,只可存取的一組特定的功能。
Consequently, the compiler and linker are able to make sensible choices concerning the placement of the variables in memory.
因此,編譯器和連接器能作出明智選擇的位置有關的變數在記憶體中。
For instance, with static variables, the compiler/linker may choose to place all of the static variables in a module in contiguous locations, thus increasing the chances of various optimizations, such as pointers being simply incremented or decremented instead of being reloaded.
例如,靜態變數,編譯器/連接器可能會選擇把所有的靜態變數在相鄰的一個函數的位置,從而增加了各種最佳化,如指標簡單遞增或遞減,而不是增值。
In contrast, global variables are often placed in memory locations that are designed to optimize the compiler’s hashing algorithms, thus eliminating potential optimizations.
與此相反,全域變數往往是放置在記憶體位置,目的是最佳化編譯器的散列算法,從而消除潛在的最佳化。


 


Static functions
靜態函式
A static function is only callable by other functions within its module.
靜態函式只可被其他函式呼叫。
While the use of static functions is good structured programming practice, you may also be surprised to learn that static functions can result in smaller and/or faster code.
而使用靜態函式是良好的結構化編程實踐中,您可能還驚訝地得知,靜態函式可能會導致小 和/或 更快的程式碼。
This is possible because the compiler knows at compile time exactly what functions can call a given static function.
這是可能的,因為編譯器在編譯時知道什麼功能都可以呼叫一個特定的靜態函式。
Therefore, the relative memory locations of functions can be adjusted such that the static functions may be called using a short version of the call or jump instruction.
因此,相對位置的記憶功能可以調整,使靜態的功能可能被要求使用短版本的呼叫或跳轉指令。
For instance, the 8051 supports both an ACALL and an LCALL op code.
例如, 8051支持一個ACALL和LCALL運算程式碼。
ACALL is a two-byte instruction, and is limited to a 2K address block.
ACALL是兩個位元組的指令,並僅限於2K的地址塊。
LCALL is a three-byte instruction that can access the full 8051 address space.
LCALL是三個位元組的指令,能夠獲得充分的8051地址空間。
Thus, use of static functions gives the compiler the opportunity to use an ACALL where otherwise it might use an LCALL.
因此,使用靜態函式給出了編譯機會使用ACALL另有它可能使用LCALL 。


The potential improvements are even better, in which the compiler is smart enough to replace calls with jumps.
改進的潛力,甚至更好,讓編譯器可以取代呼叫JUMP。


For example:


void fa(void)
{
 ...
 fb();
}


static void fb(void)
{
 ...
}


In this case, because function fb() is called on the last line of function fa(), the compiler can replace the call with a jump.
在這種情況下,因為fb()是所謂的最後一行的函數fa(),編譯器可以代替呼籲與跳轉。
Since fb() is static, and the compiler knows its exact distance from fa(), the compiler can use the shortest jump instruction.
fb() 是靜態的,編譯器知道其確切fa()的距離,編譯器可以使用最短的跳轉指令。
For the Dallas DS80C320, this is an SJMP instruction (two bytes, three cycles) vs.an LCALL (three bytes, four cycles).
DS80C320的Dallas,這是一個SJMP指示(兩個位元組, 3個週期)對 一個LCALL ( 3位元組, 4個週期) 。


On a recent project, rigorous application of the static modifier to functions resulted in about a 1% reduction in code size.
最近的一個項目,嚴格實行靜態修飾的函式造成了約1 % ,減少程式碼大小。
When your ROM is 95% full, a 1% reduction is most welcome
!當您的ROM已經用了95%,減少了1%也不無小補!


A final point concerning static variables and debugging: for reasons that I do not fully understand, with many in-circuit emulators that support source-level debug, static variables and/or automatic variables in static functions are not always accessible symbolically.
As a result, I tend to use the following construct in my project-wide include file:
最後一點是關於靜態變數和除錯:的原因,我不完全了解,許多在內部電路模擬器,支持源程式碼級除錯,靜態變數和/或自動變數的靜態函式並不總是獲得象徵性的。
所以我傾向於使用下列項目的建設在我的專案包含文檔:


#ifndef NDEBUG
#define STATIC
#else
#define STATIC static
#endif


I then use STATIC instead of static to define static variables, so that while in debug mode, I can guarantee symbolic access to the variables.
然後我使用靜態而不是靜態的定義靜態變數,因此,雖然在除錯模式下,我可以保證獲得象徵性的變數。



C's volatile keyword
C的volatile關鍵字
A volatile variable is one whose value may be changed outside the normal program flow.
一個volatile變數之,其數值是在正常程序之外可被改變的。
In embedded systems, the two main ways that this can happen is either via an interrupt service routine, or as a consequence of hardware action (for instance, a serial port status register updates as a result of a character being received via the serial port).
在嵌入式系統中,兩個主要的方式,可能會發生這種情況 一是可以通過中斷服務程序,或由於硬體的行動(例如,串列埠狀態寄存器更新由於性質收到通過串口) 。
Most programmers are aware that the compiler will not attempt to optimize a volatile register, but rather will reload it every time.
大多數程式設計師都知道,編譯器不會試圖最佳化一個volatile變數,但他將每次都被重新載入。
The case to watch out for is when compiler vendors offer extensions for accessing absolute memory locations, such as hardware registers.
這個例子是注意是當廠商編譯器提供的擴展存取絕對記憶體位置,如硬體寄存器。
Sometimes these extensions have either an implicit or an explicit declaration of volatility and sometimes they don’t.
有時,這些擴展已隱含或明確宣布不穩定性,有時他們沒有。
The point is to fully understand what the compiler is doing.
問題的關鍵是充分了解的編譯器是什麼做的事情。
If you do not, you may end up accessing a volatile variable when you don’t want to and vice versa.
如果你沒有,你可能會進入一個不穩定的變數時,你不想反之亦然。
For example, the popular 8051 compiler from Keil offers two ways of accessing a specific memory location. The first uses a language extension, _at_, to specify where a variable should be located.
例如,流行的8051編譯的Keil提供了兩種方式存取一個特定的記憶體位置。首次使用的語言擴展, _at_ ,若要指定一個變數應設。
The second method uses a macro such as XBYTE[] to dereference a pointer.
第二種方法使用一個巨集,如XBYTE [ ]來解除對一個指標的管制。
The “volatility” of these two is different.
在“volatility”這兩個是不同的。


For example:


UCHAR status_register _at_ 0xE000;



This method is simply a much more convenient way of accessing a specific memory location.
這種方法是一種更便捷的方式存取一個特定的記憶體位置。
However, volatile is not implied here. Thus, the following code is unlikely to work:
然而,隱含波動不是在這裡。因此,下面的程式碼是不可能的工作:


while (status_register); /* Wait for status register to clear */


 
Instead, one needs to use the following declaration:
相反,需要使用如下宣告:


volatile UCHAR status_register _at_ 0xE000;


 
The second method that Keil offers is the use of macros, such as the XBYTE macro, as in:
第二種方法是Keil公司提供的是使用巨集,如XBYTE巨集,如:


status_register = XBYTE[0xE000];


Here, however, examination of the XBYTE macro shows that volatile is assumed:
在這裡,但是,檢查結果表明, XBYTE巨集假設:


#define XBYTE ((unsigned char volatile xdata*) 0)


(The xdata is a memory space qualifier, which isn’t relevant to the discussion here and may be ignored.)
(在xdata是記憶體空間配置標誌,這是不相關的討論 在這裡可能會被忽略。)


Thus, the code:
因此,程式碼:


while (status_register); /* Wait for status register to clear */


will work as you would expect in this case.
在這種情況下將成為你希望狀態。
However, in the case in which you wish to access a variable at a specific location that is not volatile, the use of the XBYTE macro is potentially inefficient.
然而,在例子中,您要存取的變數在特定的位置,不是volatile,使用XBYTE巨集可能是效率低下。


C's const keyword
C的常量關鍵字
The keyword const, which is by the way the most badly named keyword in the C language, does not mean "constant"!
關鍵字常量,在C語言這是由最拙劣的關鍵字,並不意味著“常數” !
Rather, it means "read only". In embedded systems, there is a huge difference, which will become clear.
相反,它意味著“只讀” 。在嵌入式系統中,存在著巨大的差異,這將變得很清楚。


Many texts recommend that instead of using manifest constants, one should use a const variable.
許多文本建議,而不是使用明顯的常量,應該使用一個常量變數。


For instance:


const UCHAR nos_atod_channels = 8;


instead of
不是


#define NOS_ATOD_CHANNELS 8


The rationale for this approach is that inside a debugger, you can examine a const variable (since it should appear in the symbol table), whereas a manifest constant isn’t accessible.
這一方法是,在一個除錯器,您可以檢查constant變數(因為它應該會出現在符號表) ,而constant的是無法存取的。
Unfortunately, on many eight-bit machines you’ll pay a significant price for this benefit.
不幸的是,許多8位機器,您所需支付的額外的代價。
The two primary costs are:
有兩個主要代價:


The compiler creates a genuine variable in RAM to hold the variable.
編譯器將建立一個真正在RAM的變數。
On RAM-limited systems, this can be a significant penalty
在記憶體有限的系統,這可能是一項重大的代價
Some compilers, recognizing that the variable is const, will store the variable in ROM.
一些編譯器,認識到變數是constant,將存儲在ROM 。
However, the variable is still treated as a variable and is accessed as such, typically using some form of indexed addressing.
然而,變數仍被視為一個變數和存取等,通常使用某種形式的索引處理。
Compared to immediate addressing, this method is normally much slower
相比,立即定址,這種方法通常是慢得多
I recommend that you eschew the use of const variables on 8-bit micros, except in the following circumstances.
我建議您避免使用constant變數對8位元微處理器,但在下列情況下。


const function parameters
constant函數參數
Declaring function parameters const whenever possible not only makes for better, safer code, but also has the potential for generating tighter code.
宣告函數參數為constant盡可能不僅使為更好,更安全的程式碼,但也有可能產生更緊密的程式碼。
This is best illustrated by an example:
這是最好的說明了一個例子:


void output_string(CHAR *cp)
{
 while (*cp) putchar(*cp++);
}


void demo(void)
{
 char *str = "Hello, world";
 output_string(str);


 if (‘H’ == str[0]) { some_function(); }
}


In this case, there is no guarantee that output_string() will not modify our original string, str.
在這種情況下,也不能保證output_string()不會修改原來的字串str。
As a result, the compiler is forced to perform the test in demo().
因此,編譯器是被迫進行demo()的測試 。
If instead, output_string is correctly declared as follows:
如果相反,是正確output_string宣布如下:


void output_string(const char *cp)
{
 while (*cp) putchar(*cp++);
}


then the compiler knows that output_string() cannot modify the original string str, and as a result it can dispense with the test and invoke some_function() unconditionally.
然後編譯器知道output_string()不能修改原始字串str,因此,它可以無條件地免除測試和呼叫some_function()。
Thus, I strongly recommend liberal use of the const modifier on function parameters.
因此,我強烈建議自由使用常量修飾符的功能參數。


const volatile variables
常量不穩定的變數
We now come to an esoteric topic.
我們現在一個深奧的課題。
Can a variable be both const and volatile, and if so, what does that mean and how might you use it?
可以將兩個變數常量和動盪,如果是的話,這意味著什麼和如何使用它?
The answer is, of course, yes (why else would it have been asked?), and it should be used on any memory location that can change unexpectedly (hence the need for the volatile qualifier) and that is read-only (hence the const).
答案當然是肯定的(為什麼別人將它已要求? ) ,它應該被用來對任何記憶體位置,可以變更意外(因此需要動盪預選賽) ,這是只讀(因此常量) 。
The most obvious example of this is a hardware status register.
最明顯的例子,這是一個硬體狀態寄存器。
Thus, returning to the status_register example above, a better declaration for our status register is:
因此,返回status_register上述範例中,一個更好的宣告狀態暫存器方法是:


const volatile UCHAR status_register _at_ 0xE000;


Typed data pointers
型態資料指標
(意指強制指定型態 如51的data idata xdata 使編譯器可編譯成更簡潔的組語)
We now come to another area in which a major trade-off exists between writing portable code and writing efficient code—namely the use of typed data pointers ,
???which are pointers that are constrained in some way with respect to the type and/or size of memory that they can access.
我們現在討論另一方面,如何在 便攜式程式碼與寫高效率的程式碼之間做衡量,即利用型態資料指標 ,
這些指標是在某種程度上制約方面的類型和/或規模記憶,他們可以存取。
For example, those of you who have programmed the x86 architecture are undoubtedly familiar with the concept of using the __near and __far modifiers on pointers.
These are examples of typed data pointers.
例如,如果您有x86架構的程式,無疑是熟悉的概念,使用__near和__far修飾語的指標。
這些例子型態資料指標。
Often the modifier is implied, based on the memory model being used.
基於記憶模型被使用上常常是隱含的修改。
Sometimes the modifier is mandatory, such as in the prototype of an interrupt handler:
有時是強制性的修飾,如在原型中斷處理程序中:


void __interrupt __far cntr_int7();


The requirement for the near and far modifiers comes about from the segmented x86 architecture.
他要求near,far修飾語來取得x86架構記憶體區段。
In the embedded eight-bit world, the situation is often far more complex.
在嵌入式8位元的世界中,情況往往要複雜得多。
Microcontrollers typically require typed data pointers because they offer a number of disparate memory spaces, each of which may require the use of different addressing modes.
微控制器通常需要輸入的資料指標,因為它們提供了一些不同的記憶體空間,每一個可能需要使用不同的處理方式。
The worst offender is the 8051 family, with at least five different memory spaces.
最嚴重的是8051系列,其中至少有5種不同的記憶體空間。
However, even the 68HC11 has at least two different memory spaces (zero page and everything else), together with the EEPROM, pointers to which typically require an address space modifier.
然而,即使68HC11至少有兩個不同的記憶體空間(零頁(zero page)和其他一切) ,再加上電可擦除只讀存儲器,分球,這通常需要一個地址空間修飾。


The most obvious characteristic of typed data pointers is their inherent lack of portability.
最明顯的特點,型態資料指標是其固有的缺乏可移植性。
They also tend to lead to some horrific data declarations.
他們還往往導致一些可怕的數據宣告。
For example, consider the following declaration from the Whitesmiths 68HC11 compiler:
例如,考慮下面的宣告從Whitesmiths 68HC11編譯:


@dir INT * @dir zpage_ptr_to_zero_page;


This declares a pointer to an INT.
這一宣布是指向一個int 。
However, both the pointer and its object reside in the zero page (as indicated by the Whitesmith extension, @dir).
不過,這兩種指標及其對象居住在零頁(zero page)(所顯示Whitesmith延長, @dir) 。
If you were to add a const qualifier or two, such as:
如果你添加一個或兩個const資格,如:


@dir const INT * @dir const constant_zpage_ptr_to_constant_zero_page_data;


then the declarations can quickly become quite intimidating. Consequently, you may be tempted to simply ignore the use of typed pointers.
隨後的宣告可以變得非常可怕。因此,您可能會完全忽略利用指標型別。
Indeed, coding an application on a 68HC11 without ever using a typed data pointer is quite possible.
實際上,68HC11編碼的應用在卻從來不使用的型態資料指標。
However, by doing so the application’s performance will take an enormous hit because the zero page offers considerably faster access than the rest of memory.
然而,這樣的應用對性能將一個巨大的打擊,因為零頁(zero page)提供了比其他的記憶體相當快速的存取。


This area is so critical to performance that all hope of portability is lost.
對於性能是非常關鍵的地方,將沒有所有希望的可攜性。
For example, consider two leading 8051 compiler vendors, Keil and Tasking.
例如,考慮兩個主要的8051編譯器供應商, Keil和Tasking。
Keil supports a three-byte generic pointer that may be used to point to any of the 8051 address spaces, together with typed data pointers that are strictly limited to a specific data space.
Keil公司支持3位元組通用指標可以用來指向任何的8051地址空間,同時型態資料指標是嚴格限制在特定的資料空間。
Keil strongly recommends the use of typed data pointers, but doesn’t require it.
Keil公司強烈建議使用型態資料指標,但並不需要它。
By contrast, Tasking takes the attitude that generic pointers are so horribly inefficient that it mandates the use of typed pointers (an argument to which I am extremely sympathetic).
相比之下,Tasking認為通用指標是如此可怕,他們命令使用類型的資料指標(這點我感到非常同情) 。


To get a feel for the magnitude of the difference, consider the following code, intended for use on an 8051:
要感受的規模差異,請考慮下面用於8051 的程式碼:


void main(void)
{
 UCHAR array[16];  /* array is in the data space by default */
 UCHAR data * ptr = array; /* Note use of data qualifier */
 UCHAR i;


 for (i = 0; i < 16; i++)
  *ptr++ = i;
}


Using a generic pointer, this code requires 571 cycles and 88 bytes.
使用通用指標,此程式碼需要571週期和88位元組。
 Using a typed data pointer, it needs just 196 cycles and 52 bytes.
使用型態資料指標,它只需196週期和52位元組。
(The memory sizes include the startup code, and the execution times are just those for executing main()).
(記憶體大小包括啟動程式碼,並執行時間只是那些執行的Main()) 。


With these sorts of performance increases, I recommend always using explicitly typed pointers, and paying the price in loss of portability and readability.
這些類型的性能提升,我建議明確輸入總是使用資料指標,並為此付出代價的就是損失便攜性和可讀性。


Implementing an assert() macro
實施assert()巨集


The assert() macro is commonly used on PC platforms, but almost never used on small embedded systems.
Assert()巨集是常用的PC平台上,但幾乎從未使用過小的嵌入式系統。


There are several reasons for this:
有幾個原因:


Many reputable compiler vendors don’t bother to supply an assert macro
許多著名的編譯器廠商不必提供一個斷言巨集
Vendors that do supply the macro often provide it in an almost useless form
供應商供應的巨集觀調控做往往提供在一個幾乎無用的形式
Most embedded systems don’t support a stderr to which the error may be printed
大多數嵌入式系統不支持輸出到這些錯誤
These limitations notwithstanding, it’s possible to gain the benefits of the assert() macro on even the smallest systems if you’re prepared to take a pragmatic approach.
儘管存在這些限制,有可能獲得的利益的assert()巨集觀上即使是最小的系統,如果您願意採取務實的態度。


Before I discuss possible implementations, mentioning why assert() is important (even in embedded systems) is worthwhile.
在我討論可能的實施,提為什麼assert()是重要的(即使是在嵌入式系統)是值得的。
Over the years, I’ve built up a library of drivers to various pieces of hardware such as LCDs, ADCs, and so on.
多年來,我已經建立了一個函式庫的司機各種硬體如LCD ,類比數位轉換器,等等。
These drivers typically require various parameters to be passed to them.
這些驅動程序通常需要各種參數傳遞給它們。
For example, an LCD driver that displays a text string on a panel would expect the row, the column, a pointer to the string, and perhaps an attribute parameter.
例如, LCD驅動器,顯示的文本字元串的一個小組預計該行,該列,一個指向字元串,也許屬性參數。
When writing the driver, it is obviously important that the passed parameters are correct.
寫作時的驅動程序,這顯然是重要的是,通過參數是正確的。
One way of ensuring this is to include code such as this:
方法之一是確保這是包括程式碼象這樣:


void Lcd_Write_Str(UCHAR row, UCHAR column, CHAR *str, UCHAR attr)
{
 row &= MAX_ROW;
 column &= MAX_COLUMN;
 attr &= ALLOWABLE_ATTRIBUTES;
 if (NULL == str) return; /* The real work of the driver goes here */
}



This code clips the parameters to allowable ranges, checks for a null pointer assignment, and so on.
此程式碼片斷的參數允許的範圍內,檢查空指標轉讓,等等。
However, on a functioning system, executing this code every time the driver is invoked is extremely costly.
然而,在一個正常運作的系統,執行此程式碼的驅動程序每一次呼叫是極其昂貴的。
But if the code is discarded, reuse of the driver in another project becomes a lot more difficult because errors in the driver invocation are tougher to detect.
但是,如果程式碼被丟棄,重複使用的驅動程序中的另一個項目變得更加困難了很多錯誤,因為該驅動程序呼叫的更嚴厲的檢測。


The preferred solution is the liberal use of an assert macro. For example:
是最可取的辦法是自由使用斷言巨集。例如:


void Lcd_Write_Str(UCHAR row, UCHAR column, CHAR *str, UCHAR attr)
{
 assert (row < MAX_ROW);
 assert (column < MAX_COLUMN);
 assert (attr < ALLOWABLE_ATTRIBUTES);
 assert (str != NULL); /* The real work of the driver goes here */
}


This is a practical approach if you’re prepared to redefine the assert macro.
這是一個切實可行的辦法如果你是準備重新斷言巨集。
The level of resources in your system will control the sophistication of this macro, as shown in the examples below.
資源的水平在您的系統將會控制這個複雜的巨集觀調控,如下面的例子。


Assert 1
斷言1
This example assumes that you have no spare RAM, no spare port pins, and virtually no ROM to spare.
本示例假定您有沒有多餘的記憶體,沒有多餘的端口引腳,幾乎沒有光盤,以備用。
In this case, assert.h becomes:
在這種情況下, assert.h變成:


#ifndef assert_h
 #define assert_h
 #ifndef NDEBUG
  #define assert(expr) \ if (expr) {\ while (1);\ }
 #else
  #define assert(expr)
 #endif
#endif


Here, if the assertion fails, we simply enter an infinite loop.
The only utility of this case is that, assuming you’re running a debug session on an ICE, you will eventually notice that the system is no longer running.
在這裡,如果斷言失敗,我們只需輸入一個無限循環。
唯一的效用是,這種情況下,假設你正在用ICE除錯時,你最終會發現,系統不再執行。
In which case, breaking the emulator and examining the program counter will give you a good indication of which assertion failed.
在這種情況下,打破了模擬器和審查程序計數器會給你一個很好的跡象,其中斷言失敗。
As a possible refinement, if your system is interrupt-driven, inserting a “disable all interrupts” command prior to the while(1) may be necessary, just to ensure that the system’s failure is obvious.
作為一個可能的改進,如果您的系統中斷驅動,插入一個“禁用所有中斷”命令之前,while(1)可能是必要的,只是為了確保該系統的失敗是顯而易見的。


Assert 2
斷言2


This case is the same as assert #1, except that in example #2 you have a spare port pin on the microcontroller to which an error LED is attached.
這起例子是一樣的assert #1 ,但在example #2你有一個備用端口引腳微控制器上的其中一個錯誤致使附後。
This LED is lit if an error occurs, thus giving you instant feedback that an assertion has failed.
這種發光二極管亮如果出現錯誤,從而為您的即時反饋,一個斷言失敗。
Assert.h now becomes: Assert.h
現已成為:


#ifndef assert_h
 #define assert_h
 #define ERROR_LED_ON()  /* Put expression for turning LED on here */
 #define INTERRUPTS_OFF() /* Put expression for interrupts off here */
 #ifndef NDEBUG
  #define assert(expr) \ if (expr) {\ ERROR_LED_ON();\ INTERRUPTS_OFF();\ while (1);\ }
 #else
  #define assert(expr)
#endif #endif


Assert 3
斷言3
This example builds on assert #2.
這個例子基於assert #2。
But in this case, we have sufficient RAM to define an error message buffer, into which the assert macro can sprintf() the exact failure.
但是,在這種情況下,我們有足夠的記憶體來定義錯誤信息的緩衝區,將其主張巨集觀可以sprintf()的確切失敗。
While debugging on an ICE, if a permanent watch point is associated with this buffer, then breaking the ICE will give you instant information on where the failure occurred.
當用ICE除錯時,如果一個常任觀賞點是與此相關的緩衝區,然後ICE的斷點立即給您故障發生的信息。
Assert.h for this case becomes: Assert.h
的這種情況下會變成:


#ifndef assert_h
 #define assert_h
 #define ERROR_LED_ON()   /* Put expression for turning LED on here */
 #define INTERRUPTS_OFF() /* Put expression for interrupts off here */
 #ifndef NDEBUG extern char error_buf[80];
  #define assert(expr) \ if (expr) {\ ERROR_LED_ON();\ INTERRUPTS_OFF();\ sprintf(error_buf,”Assert failed: “ #expr “ (file %s line %d)\n”, __FILE__, (int) __LINE__ );\ while (1);\ }
 #else
  #define assert(expr)
 #endif
#endif


Obviously, this requires that you define error_buffer[80] somewhere else in your code.
顯然,這需要您確定error_buffer[80]在您的程式碼的其他地方。


I don't expect that these three examples will cover everyone’s needs.
我不預期這三個例子可以解決每個人的需要。
Rather, I hope they give you some ideas on how to create your own assert macros to get the maximum debugging information within the constraints of your embedded system.
相反,我希望可以給你一些意見,如何建立自己的斷言巨集,以在嵌入式系統的限制下獲得最大的除錯信息。



Recursion
遞迴
Recursion is a wonderful technique that solves certain problems in an elegant manner.
遞迴是一種非常棒的技術,解決了某些問題在一個優美的方式。
It has no place on an eight-bit microcontroller. The reasons for this are quite simple:
在8位微控制器它已沒有立足之地的。造成這種情況的原因很簡單:


Recursion relies on a stack-based approach to passing variables. Many small machines have no hardware support for a stack.
遞迴依靠一個基於堆疊的方法來傳遞變數。許多小系統沒有硬體支持的堆疊。
Consequently, either the compiler will simply refuse to support reentrancy, or else it will resort to a software stack in order to solve the problem, resulting in dreadful code quality
因此,無論是簡單的編譯器將拒絕支持重入,否則將使用軟體堆疊,以解決這個問題,但將造成可怕的程式碼質量
Recursion relies on a “virtual stack” that purportedly has no real memory constraints.
遞迴依賴於“虛擬堆疊”這看來已沒有真正的記憶體限制。
How many small machines can realistically support virtual memory?
有多少現實的小型機器可以支持虛擬記憶體?
If you find yourself using recursion on a small machine, I respectfully suggest that you are either (a) doing something really weird, or (b) you don’t understand the sum total of the constraints with which you’re working.
如果您發現使用遞迴的一個小機器,我謹建議,除非你您是 A.做真正奇怪的事情,B.你不理解你再做啥。
If it is the former, then please contact me, as I will be fascinated to see what you are doing.
如果是前者,那麼請與我聯繫,我將很有興趣去看你如何做。


 


Variable length argument lists
可變長度參數列表
You should avoid variable length argument lists because they too rely on a stack-based approach to passing variables.
您應該避免可變長度參數列表,因為他們太依賴於一個基於堆疊的方法來傳遞變數。
What about sprintf() and its cousins, you all cry?
如 sprintf() 和其他相關的函式
Well, if possible, you should consider avoiding the use of these library functions. The reasons for this are as follows:
以及,如果可能的話,你應該考慮避免使用這些函式庫的功能。造成這種情況的原因如下:


If you use sprintf(), take a look at the linker output and see how much library code it pulls in. On one of my compilers,
sprintf(), without floating-point support, consumes about 1K. If you’re using a masked micro with a code space of 8K, this penalty is huge
如果您使用的sprintf(),看看輸出的連接,看看有多少函式庫程式碼拉進來在我的編譯器,
sprintf(),沒有浮點支持,消耗約1000 。如果您要將程式嵌入到IC內,空間的8K型,這是巨大的代價
On some compilers, use of sprintf() implies the use of a floating-point library, even if you never use the library.
在一些編譯器,使用sprintf()意味著使用浮點函式庫,即使你從來沒有使用函式庫。
Consequently, the code penalty quickly becomes enormous
因此,程式碼的代

arrow
arrow
    全站熱搜

    立你斯 發表在 痞客邦 留言(1) 人氣()