進入目前的公司當韌體工程師也過了幾個月
算是有一些小心得
想說紀錄一下
有緣看到的新手韌體工程師看到也可以參考參考
或許可以少走一些彎路,接軌或面試可能會順利一點點
===
先介紹我的背景
非資工、資管本科,寫程式純自學的
依序接觸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)
留言列表