進入目前的公司當韌體工程師也過了幾個月

算是有一些小心得

想說紀錄一下

有緣看到的新手韌體工程師看到也可以參考參考

或許可以少走一些彎路,接軌或面試可能會順利一點點

===

先介紹我的背景

非資工、資管本科,寫程式純自學的

依序接觸Arduino => 8051 => ARM

(ARM搞最久,前兩個都蜻蜓點水,各碰個兩三個月而已)

邊看書、教學影片邊做實驗、邊研究example code

前前後後搞了一年左右

後來進入了一家小的系統廠,也就是現在的公司

不過部門主管是寫code魔人,所以偷學了不少撇步

最近看到不少來面試的新人好像連筆試都過不了

想說來分享分享一些老手覺得會很正常,但新手其實不知道的C語言技巧

===

我的工作主要是寫單晶片的韌體,當然就是用C寫了

目前碰過的晶片有:ARM-M0+(32位元)、PIC(16位元)、也有8位元的晶片

主要開發ARM-M0+,其它的是維護而已

 

講一下我的C語言學習路程

進入公司之前的C語言都是自學的,圖書館借了書拿來看

那種『新手C語言教程』之類的,看了有2、3本

第一本比較苦手,第2、3本就只看自己沒看過的地方而已

看ARM的影片教程時,也參考開發板官方提供的code

自己覺得自己也看不出有什麼不足之處時,就開始找工作了

想說邊面試可能就會知道自己缺什麼東西

這篇主要想提提在C語言方面,實際進公司後碰到的狀況,跟看書學的差異在哪

(面試時要會什麼的部分,之前在其他篇好像寫過了

這邊就先不提了,之後有想到再補上)

 

===

 

1. Union

首先,我學C語言時,本來覺得union是個沒用的東西

好像也用不太到,也不知道有什麼應用場景

想說應該是個垃圾語法

所以當時學的時候,就不太管他

但其實,union很方便

進公司後很常用到,一些廠商提供的code也都會看到union

所以建議各位一定要學起來(筆試應該也會看到啦)

他最常用的場景是下面這種:

typedef struct {

   u8 A;

   u8 B;

   u8 C;

   u8 D;

} XXX_t;

XXX_t tXXX;

(PS: u8 是unsigned char)

假如你希望可以方便把資料寫入結構體tXXX的成員A、B、C、D的話

(譬如處理封包時,得到的會是一串資料,這時一個一個成員給資料就顯得有點笨)

你會把上面那個結構改成下面這個樣子

typedef union

{

   struct {

      u8 A;

      u8 B;

      u8 C;

      u8 D;

   } XXX_t;

   u8 Buffer[4];

} YYY_t ;

YYY_t  tYYY;

這樣你只要對Buffer進行寫入

就可以把資料個別填入成員A、B、C、D中

像下面這樣:

memcpy(tYYY.Buffer, pSource, 4);  // Buffer是目的陣列的地址,pSource是來源陣列的地址

把Source的資料寫入Buffer,長度為4,就搞定了

PS1: 上面的長度直接寫4,通常也是不建議的寫法,這樣會有魔法數字的問題

即別人看到你的code,會不知道4是什麼東西

所以建議寫成:memcpy(tYYY.Buffer, pSource, sizeof(tYYY.Buffer));

有關魔法數字的部分,建議新手要特別注意,可以查一查相關的文來看

 

PS2: <string.h>裡的memcpy、memset其實蠻常用的,建議要學一下

其他的函數...就沒在用...可以把時間省下來,有需要再看

rand函數我考試有碰過

file處理的部分,應徵韌體工程師就沒看過了,應該可以不用管

 

2. 函數指標

這個筆試也很愛考

如何定義函數指標,這個要會

然後別人定義了一個函數指標,你要能解釋

這也是萬年必考題,例如:

typedef void (*pfXXXX)(void);

=> pfXXXX是一個"指向『參數為void』、『回傳值為void』的函數 的指標"

雖然很拗口,不過就是要能夠解讀得出來

 

那麼實際上這玩意到底實際上有沒有人在用呢?

答案是:非常常見!

廠商提供的原始碼中都會有這種code

看不懂,你東西就用的霧沙沙啦~

所以這是個必須要會的東西,逃不掉的~

 

那這玩意的好處到底在哪裡?

為什麼有經驗的工程師都會使用這玩意?

答案就是:使用函數指標,可以把code變短很多

據我老闆的解釋,大致是這樣:

如果沒有使用函數指標,就得用一堆條件判斷

 

不過這東西好用歸好用

架構起來還是需要一些經驗

例如,函數指標規定傳入值、回傳值都要是長一個樣的

所以要指到的函數都要稍微設計一下傳入值、回傳值

或是乾脆用一個結構體替代

 

總之,新手大概看得懂就夠了

一開始就要求會用函數指標來架構程式是不太現實的

 

另外,函數指標在debug時也不太方便

不像一般函數一樣,直接跳轉到定義就可以看到是什麼

也是個麻煩的點

 

3. 位元運算

這個新手可能也會跳過去

或不知道是不是常用

答案是:非常常用

講講我目前碰到過的地方:

a. 大小端問題

b. 位元組的儲存規劃

下面分別解釋

 

a. 大小端問題(Big Endian、Little Endian)

大小端問題主要是:

傳封包時,通常使用大端在傳

可是MCU常常是小端的

處理資料就必須存成小端的

或是在EEPROM中,是用大端在存資料

可是取出來時,又是用小端在使用資料

這時就必須保證取出來的資料是對的

就必須需要處理大小端的問題

 

譬如如果收到一個封包,裏面有個資料是2 Bytes的

就可能需要組封包,像是:

收到0x0118:Data[2]= [0x01][0x18]

第一個byte是0x01,第二個Byte是0x18

這個比較簡單,簡單組一下就好了:

u16 NewData;

NewData= ((u16) Data[0]<<8) | Data[1];

(這邊要注意要強制轉型,沒轉型的話

Data[0]<<8,只會得到0而已)

 

有時腦袋不清楚搞混了

組反了,就會弄出0x1801這種情況

顯然是不行的(這個筆試也愛考)

 

b. 位元組的儲存規劃

常用的位元組規劃方式

是用一個unsigned charater (u8)或  unsigned int (u16)

去儲存設定

舉一個的簡單地例子

例如現在有個:

u8 Data;

看起來他就是個資料,有bit 0~bit 7,共8個bit

但我們可以規劃某些bit用來儲存狀態

例如bit 7是開關狀態,bit 5、6是模式之類的

只使用bit 0~4來存資料

當然這時只有5個bit,最大就就只能表示到31而已

這就要自己拿捏一下(或是用u16來存)

但好處是,傳值的時候

因為所有資訊通通包在一個參數裡

只要傳1個參數,就可以同時把數值、模式、開關等資料傳出去

(當然也可以用結構體、位元欄位等,就看你怎麼規劃了)

 

這時要把狀態存進去,就要用到位元運算

例如,要把開關的狀態存到bit7去,會像下面這樣寫:

#define ON     1

#define OFF   0

#define STATUS_FLAG    (ON<<7)  

u8 Data;

Data |= STATUS_FLAG

 

如果拿到資料

要把開關狀態取出來,就要:

#define MASK_STATUS     (1<<7)

u8 Status;

Status = (Data & MASK_STATUS)>>7;

或是

Status = (Data & MASK_STATUS)?1:0;

 

 

或是當開關狀態為ON時才進行某些動作:

if(Data & MASK_STATUS) // 不為0才動作

{

   // Do something, ex:點亮LED燈多久

}

上面牽扯到遮罩的技巧,也是一個蠻常用的技巧

 

另外,筆試時也很愛考

像是考你把某個位元改成遮罩:

(1<<7) // 1000 0000

(1<<7) | (1<<6) // 1100 0000

(1<<7) | (1<<3) // 1000 1000

~(1<<7) // 1000 0000 => 0111111

 

通常,上面的位元是會取名字的,不然魔法數字真的很難看的懂,例如:

#define STATUS_FLAG  (1<<7)

#define COLOR_FLAG  (1<<6)

#define LED_CONTROL  (STATUS_FLAG  | COLOR_FLAG  )

總之,位元運算是個考試很常考,進公司也很常用的東西,一定要會~

不只要會,還要熟練~

 

 

4. 無窮迴圈問題

是說你有沒有辦法看到一個迴圈的code就判斷出是否會有無窮迴圈的問題

一般聽到無窮迴圈可能會覺得沒什麼,名字還覺得有點酷炫,可能會覺得是好事吧~

實際情況是:你的程式如果會陷入無窮迴圈,然後這程式灌在你公司的產品裡

會發生什麼事?

答案是:陷入無窮迴圈時,對客人來說,你家產品就跟陷入當機是一樣的

也就是說,你把產品搞砸了,等著被老闆X吧~

也就是一個非常糟糕的問題

 

所以,必須要保證你自己寫的code不會陷入無窮迴圈中

 

所以,看到迴圈,必須要看code就能判斷出是否有無窮迴圈的問題

(我老闆表示:必須具備『不依賴compiler』就能看出code有錯誤的能力

就算用代值的方法,也必須看出code是否有問題

=> 這樣你就會被錄取,因為大部分的新手都不會具備這種能力)

 

但這要舉例就比較難了,因為通常會看不出來的狀況是

迴圈的code很長,或是改變中斷迴圈條件的地方在code的其他地方

就是蠻吃經驗的地方

我老闆是蠻愛考這個的,說是一題就可以把有無經驗的工程師區分出來

 

通常,避免無窮迴圈的方法是

設定一個最大上限,達到此上限就強制跳出該迴圈

適用於某些硬體可能會壞掉的狀況

例如你用了某些感測器、EEPROM,可能會燒掉、或是運作中被拔掉的東西

這時沒有加上強制跳出的條件,就會導致產品無法繼續工作

試想,如果你是消費者

你買的產品,某個部分壞了,就導致整個產品都不能用

你覺得這產品設計的好嗎?

 

加上了跳出的最大上限,就會讓你的code更『強壯』一點

讓你的產品更像個成熟的產品

 

像我之前要用某個感測器,從github上下載了別人寫的函數庫,

裡面都沒有強制跳出的設計,就比較危險

用到公司的產品時,就把它通通都加上了強制跳出的判斷

以策安全

 

5. Debug

這個就跟C語言關係沒這麼大

不過是工作的必備技能

debug方式有2種:

a. 在code裡面加入printf,用UART取得列印資料(例如用XCOM等工具顯示)

這樣就可以知道程式是否有如預期進行到這裡

或是數值是多少,以判斷是否符合預期

如果code寫錯的話,也可能完全進不到你寫的部分

那就要往上頭追了

 

b. 用IDE裡面的單步執行

這個看書通常都沒有教,但其實超~級~重~要~

顧名思義就是在IDE中開下去debug mode

然後一直按『下一步』,看程式是否有照你寫的跑

順便還可以看組語跑到記憶體中的哪,這算比較高的技巧

如果發生MCU當機,也會看的出來

例如你寫了個1/0這種code,就可能會跳到當機的那一行去

這個是a.無法做到的

 

不過debug技術考試考不出來

只有進公司被老闆電才學得會

就先進公司再說吧~

 

====

以上是我當了幾個月的韌體工程師的實踐心得

希望對在找工作的新手有點幫助

老手若看到覺得有不對或可以補充的,也歡迎下方留言唷~

(不要筆戰,小弟很弱 X_X)

 

 

 

 

 

 

 

 

 

 

 

arrow
arrow

    迷途工程師 發表在 痞客邦 留言(0) 人氣()