# 猴子也能懂的嵌入式 ## 基礎C & 基礎Embedding ### 基本的專有名詞 * unary operators : 沒有左右兩邊的運算子例如像x++, ++x, x-\- * logical vs Bitwise : 例如: ```c= char A= 40; char B= 30 ``` * 我們做&&(logical AND)是判斷這兩個值是否都不為0,因此 ```c= C = A && B; //C = 1 A,B都不為0 ``` * 我們做&(Bitwise AND) ```c= C = A & B; /* A = 00101000 & B = 00011110 ----------- C = 00001000 */ ``` * GPIO : General Purpose IO * ADC : 用來計時 * scanf: sacnf 讀取會忽略空白,如果要完整的讀取要使用scanf("%\[\^你想要讀到最後的字元\]") ### Cross compilation * Cross compiler 是針對不同架構,經由compiler將語進行轉換生成不同的可執行檔案 例如像是.elf, .bin .hex * elf: elf(excute linker file)是可以用於excute跟link的執行檔 * .bin跟.hex .bin 跟.hex是可以執行的純二進位檔(作為產品用這個,因為elf檔可以反編譯) ### 型態 * short : 2 byte * long long int 8: byte * uint32_t : 可以自由定義參數的大小(32這個數字可以隨意替換) ### static and extern * static: 時機是在當我們相要宣告全域但是限定只能跑在本份文件的的變數,就需要使用static保護變數。如果不宣告的話,一般變數我們在別的文件中extern參數就會改變變數的值。 * extren: 代表我們在其他的文件中已經宣告了,使用extern就不用再次宣告。 舉例來說有兩份文件,a.c 跟 b.c: ```c= // a.c void func1(void); int var1 = 0; int main(){ } void func1(void){ int var1 = 100; } ``` ```c= // b.c extern int var1; void func1(void){ int var1 = 500; } ``` 因為在a文件中沒有受到保護,因此當我們在b使用extern之後就會修改var的值。 對a.c做修改,將變數加上static後可以防止別的文件存取。 ```c= // a.c void func1(void); int var1 = 0; int main(){ } void func1(void){ static int var1 = 100; } ``` ### pointer * 如果不用(char*)之類的 pointer對變數進行宣告0xABCD就單純是一串數字,只有在加入(char*)0xABCD才會變成一串地址。 * 指標永遠保留8個byte #### 宣告指標的時候讀取資料的長度 * 根據宣告的宣告的pointer的type可以決定,讀取多少字節的數據。 ``` c= // address 因為是char,讀取資料的範圍為1byte char* address = (char*)0x00007FFF8E3C3824 ``` ``` c= // address 因為是int,讀取資料的範圍為4byte int* address = (int*)0x00007FFF8E3C3824 ``` ``` c= // address long long int,讀取資料的範圍為8byte long long int* address = (long long int*)0x00007FFF8E3C3824 ``` #### 藉由char存取其他byte * 我們由上面可以得知如果我宣告char pointer存取資料,只會存取到1byte 例如 ```c= char *pAddres = (char*)4567 // *pAddres == 67 ``` 藉由指標變數存取到最後一個byte,如果我們將指標+1,則位置會移動1byte(移動多少根據需告的指標型態決定)。 ```c= char *pAddres = (char*)4567 // *pAddres == 67 pAddres = pAddres +1 // *pAddres == 45 ``` #### 不同型態pointer移動大小 * 不同型態的pointer有+1會移動不同的大小 * **short\*+1** : 移動 2byte * **int\*+1** : 移動 4byte * **long long int\*+1** : 移動 8byte * **uint32_t\*+1** : 移動 4byte(32 bit) ### const & volatile #### const 運用const去保護變數使他變成read-only * 宣告(下面兩種宣告是相同的) ```c= const uint8_t data uint8_t const data ``` * ***注意:const不代表無法改變變數值,仍然可以使用地址更改const的值*** ```c= uint8_t data = 10; uint8_t* ptr = (uint8_t*)&data; // uint8_t const* 所以需要 // 強制轉換型態變為uint8_t* *ptr = 50; //此時的data值已被更改為50 ``` 可以得知const不代表你無法更改它的值,它僅是一個保護機制,設置const可以在有人使用這個變數的時候,compiler警告有人使用者嘗試更改變數 * const會將變數存入flash中,而flash有write-protected 機制,因此無法寫入。 * **const的 case**: * **uint8_t const data** :定義一些在程式中使用的數學常數。 * **unint8_t const\* pData** : * pointer pData(\*pData)可以被改變,也就是指向的位置可以被改變,但是指向的資料不可以被修改 * 也就是pointer指向一個read-only 的data * 假設: unint8_t const* pData = (uint8_t*)0x40000000 **O** pData = (uint8_t)0x50000000 **O** pData = (uint8_t)0x70000000 **X** *pData = 50 * 使用時機當目標的值是恆定的例如: * 在Linux 的open function 裡: int open(const char* path, ... ) 但是文件讀取的路徑不該被隨意改變,但是你可以將路徑存在任意變數中。 * **uint8_t \*const pData** : * 與上一個相反,資料的內容可以被改變,但是資料的位置不能改變 * 假設: unint8_t const* pData = (uint8_t*)0x40000000 **O** *pData = 50 **O** *pData = 30 **X** *pData = (uint8_t)0x50000000 * 使用時機: * 為了提高可讀性以及保護pointer,例如: 希望建立一個表格填上大家的資料,表格的位置不該被移動,但是我希望這個位置上的資料是可以被改變的,因此會使用const保護這個pointer * **uint8_t const\*const pData**: * 不管是指標或是指標內存的值都是read-lonly的 * 使用時機: * 當我們使用暫存器的時候 #### volatile *注意:const跟volatile不是相對的,兩者可以同時使用* * compiler會自動優化程式,加上volatile之後就不會自動執行優化 ```c= uint8_t data1; uint8_t data2; data1 = 50; data2 = data1; data2 = data1; ``` 明顯兩句data2 = data1;是多餘的,在compiler中經過優化之後會只執行一次。但是ru; 將變數加上volatile之後,就會兩句都執行 ```c= uint8_t volatile data1; uint8_t volatile data2; data1 = 50; data2 = data1; data2 = data1; ``` * 使用時機: * 常常會改變的值就會使用volatile,例如像是pin腳上的值,因為pin 腳上的值時常會改變,但是compiler會認為那是恆定的值而不執行。 * 使用時機的case: * 如果memory map到register的時候 * 多任務的RTOS的時候 * 當全域變數分享給main code 跟 ISR code的時候 * volatile case: * **uint8_t volatile my_data** * **uint8_t volatile \*pStatusReg**: * 這代表 pointer不是volatile的,但是這個pointer 指向一個 volatile的data * 使用時機: * 當map 到memory register的時候使用 #### Use Const & volatile ```c= uint8_t volatile *const pReg = (uint8_t*)0x40000000; // 代表pReg的pointer用 const代表希望這邊的address是const的,但是這邊所指向的值是volatile的,代表值可能為容易改變的因此不要去讀寫 uint8_t const volatile *const pReg = (uint8_t*)0x40000000; // 前段與上面相同,但是唯一的差異是前面加入了const代表,隨然值是volatile的但是,我們還是希望不要去改它 // 使用時機: read-only register buffer ``` ### struct vs. union 在struct中大小會以4的倍數將資料對齊,不足的部分會padding到4 byte * struct: * 一般的struct: ```c= struct Mode{ } ``` * typedef struct: ```c= typdef struct{ }Mode_t ``` typedef 的struct 在使用上不需要再宣告一次struct型態。只需要直接使用即可。另外一般的typedef 的struct會在後方加上_t藉此區別。 * union: union會選擇union中最大成員的長度作為儲存的空間。只有當成員都是互相互斥的時候才可以使用union,也就是你不會同時使用成員變數的時候,例如封包傳輸,在封包傳輸中封包有長有短,不會同時傳送所以可以使用union。以下舉例struct跟Union ```c= //struct struct address{ uint16_t shortAddr;// 2 byte uint32_t longAddr;// 4 byte // |0xE00| |0xE01| |0xE02| |0xE03| |0xE04| |0xE05| |0xE06| |0xE07| // <- shortAddr -> <- padding -> <- longAddr -> } ``` ```c= //union union address{ uint16_t shortAddr;// 2 byte uint32_t longAddr;// 4 byte /* 因為元素中最常是4 byte 因此union大小為4 byte |0xE00| |0xE01| |0xE02| |0xE03| */ /* 假設我要存兩筆資料進入union中分別為,先存入short再存long shortAddr 0xABCD longAddr 0xEEEECCCC Step 1: |0xE00| |0xE01| |0xE02| |0xE03| CD AB Step 2: |0xE00| |0xE01| |0xE02| |0xE03| CC CC EE EE Step 3: print shortAddr = 0xCCCC longAddr = 0xEEEECCCC shortAddr會是0xCCCC是因為longAddr宣告時將儲存空間覆蓋了, 在讀取的時候就會讀取到前面兩個byte也就是CC */ } ``` ### bit-field 可以使用bit-field 重構整個struct的空間分配 - 假設有8bit長度的空間,但是我只想讓變數使用其中的2bit(在原始的資料中會使用會使用8bit,但是剩下的6 bit是空間浪費),會使用以下定義方法 ```c= stuct a{ uint8_t x :2; } ``` 將8 bit中的2個bit分給x - 假設我甚至可以將所有變數都塞入32 bit的空間中 ```c= typedef struct{ uint32_t a :2; uint32_t b :1;. uint32_t c :8; uint32_t d :6; uint32_t e :12; uint32_t f :3; //重新分配32 bit的空間 }Test_t ``` ### macro * macro 跟一般定義變數不同,他是一個數值。常常使用在程式中定義一些不變的常數 * macro 沒有分號 * #error可以顯示錯誤訊息 * 為了確保macro參數執行正確,會加( )來確保執行不會受其他程式干擾 例如(以下是錯誤的) ```c= #define PI_VALUE 3.1415f #define AREA_OF_CIRCLE(x) PI_VALUE * x * x int main(void){ float area_circle; area_circle = AREA_OF_CIRCLE(2); printf("Area = %f\n", area_circle) //這時候print Area = 12.566000是正確的 } ``` 但是如果我將2改為1+1 ```c= #define PI_VALUE 3.1415f #define AREA_OF_CIRCLE(x) PI_VALUE * x * x int main(void){ float area_circle; area_circle = AREA_OF_CIRCLE(1+1);//2 改為1+1 printf("Area = %f\n", area_circle) // 這時候print Area = 5.141500是錯誤的 // 因為x被改為1+1,而macro也被定義成 // PI_VALUE * 1+1 * 1+1因此值為錯誤 } ``` 所以我們必須對macro做保護,將 #define AREA_OF_CIRCLE(x) PI_VALUE * x * x -> #define AREA_OF_CIRCLE(x) ((PI_VALUE) * (x) * (x))
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up