1 位操作
位操作與位帶操作并不相同,位操作就是對一個變量的每一位做運(yùn)算,而邏輯位操作是對這個變量整體進(jìn)行運(yùn)算。
下面是六種常用的操作運(yùn)算符:
按位取反
void?test01() { int num = 7; printf("~num = %d ", ~num);//-8 // 0111 按位取反 1000 機(jī)器中存放的都是補(bǔ)碼 //補(bǔ)碼轉(zhuǎn)換原碼需要分有符號數(shù)和無符號數(shù)兩種 }
按位與
void test02() { ??int?num?=?128; //換算為八位,1換算就是00000001, 這樣只要所給數(shù)字的二進(jìn)制最后一位是1.那么就是奇數(shù),否則就是偶數(shù) if ( (num & 1) == 0) { printf("num為偶數(shù) "); } else { printf("num為奇數(shù) "); } }
按位異或
void test03() { //按位異或的意思是,兩個數(shù)字相同為0,不同為1。我們可以利用按位異或?qū)崿F(xiàn)兩個數(shù)的交換 num01 = 1; // 0001 num02 = 4; // 0100 printf("num01 ^ num02 = %d", num01 ^ num02); // 5 兩個二進(jìn)制按位異或之后是: 0101 printf("交換前 "); printf("num01 = %d ", num1); printf("num02 = %d ", num2); num01 = num01 ^ num02; num02 = num01 ^ num02; num01 = num01 ^ num02; //不用臨時數(shù)字實現(xiàn)兩個變量交換 printf("交換后 "); printf("num01 = %d ", num1); ??printf("num02?=?%d ",?num2); }
按位或
計算方法:
????參加運(yùn)算的兩個數(shù),換算為二進(jìn)制(0、1)后,進(jìn)行與運(yùn)算。只有當(dāng)?相應(yīng)位上全部為1時取1,?存在0時為0。
printf是格式化輸出函數(shù),它可以直接打印十進(jìn)制,八進(jìn)制,十六進(jìn)制,輸出控制符分別為%d, %o, %x, 但是它不存在二進(jìn)制,如果輸出二進(jìn)制,可以手寫,但是也可以調(diào)用stdlib.h里面的itoa函數(shù),他不是標(biāo)準(zhǔn)庫里面的函數(shù),但是大多數(shù)編譯器里面都有這個函數(shù)。
#include#include int main() { test04(); } int test04() { ????int?a?=?6;??????????????????//二進(jìn)制0110 ????int?b?=?3;??????????????????//二進(jìn)制0011 ????int?c?=?a?|?b;??????????????//a、b按位或,結(jié)果8,二進(jìn)制111,賦值給c char s[10]; itoa(c, s, 2); ??? printf("二進(jìn)制?-->?%s ", s);//輸出:二進(jìn)制?-->111 }
左移運(yùn)算符
void test05() { int num = 6; ??printf("%d ",?num?<3);//左移三位,就是0000 }
右移運(yùn)算符
void test06() { int num = 6; //0110 printf("%d ", num >> 1); //右移一位,就是0011,輸出3 }
上面是用普通c代碼舉得栗子,下面我們看一下STM32中操作通常用的代碼:
(1)比如我要改變 GPIOA-> BSRRL 的狀態(tài),可以先對寄存器的值進(jìn)行& 清零操作
GPIOA-> BSRRL &= 0xFF0F; //將第4位到第7位清零(注意編號是從0開始的)
然后再與需要設(shè)置的值進(jìn)行|或運(yùn)算:
GPIOA-> BSRRL |= 0x0040; //將第4位到第7位設(shè)置為我們需要的數(shù)字
(2)通過位移操作提高代碼的可讀性:
GPIOx->ODR = (((uint32_t)0x01) << pinpos);
上面這行代碼的意思就是,先將"0x01"這個八位十六進(jìn)制轉(zhuǎn)換為三十二位二進(jìn)制,然后左移"pinpos"位,這個"pinpos"就是一個變量,其值就是要移動的位數(shù)。也就是將ODR寄存器的第pinpos位設(shè)置為1。
(3)取反操作使用:
SR寄存器的每一位代表一個狀態(tài),如果某個時刻我們想設(shè)置一個位的值為0,與此同時,其它位置都為1,簡單的作法是直接給寄存器設(shè)置一個值:
TIMx->SR=0xFFF7;
這樣的作法設(shè)置第 3 位為 0,但是這樣的作法可讀性較差。看看庫函數(shù)代碼中怎樣使用的:
TIMx->SR = (uint16_t)~TIM_FLAG;
而 TIM_FLAG 是通過宏定義定義的值:
#define TIM_FLAG_Update ((uint16_t)0x0001) #define TIM_FLAG_CC1 ((uint16_t)0x0002)
2 define宏定義
define 是 C 語言中的預(yù)處理命令,它用于宏定義,可以提高源代碼的可讀性,為編程提供 方便。
常見的格式:
#define 標(biāo)識符 字符串
標(biāo)識符意思是所定義的宏名,字符串可以是常數(shù)、表達(dá)式或者格式串等,例如:
#define PLL_Q 7 //注意,這個定義語句的最后不需要加分號
3 ifdef條件編譯
在程序開發(fā)的過程中,經(jīng)常會用到這種條件編譯:
#ifdef PLL_Q 程序段1 #else 程序段2 #endif
上面這段代碼作用就是當(dāng)這個標(biāo)識符已經(jīng)被定義過,那么就進(jìn)行程序程序段1,如果沒有則進(jìn)行程序段2。當(dāng)然,和我們設(shè)計普通的c代碼是一樣的,"#else"也可以沒有,就是上面的代碼減去"#else"和程序段2。
#ifndef PLL_Q //意思就是如果沒有定義這個標(biāo)識符
4 extern變量申明
C 語言中 extern 可以置于變量或者函數(shù)前,以表示變量或者函數(shù)的定義在別的文件中,提示編譯器遇到此變量和函數(shù)時在其他模塊中尋找其定義(一個變量只能定義一次,而extern可以申明很多次)使用例子如下:
extern u16 USART_RX_STA;
上面例子意思就是申明 “USART_RX_STA” 這個變量在其他文件中已經(jīng)定義了,"u16"的意思是16位的。
5 結(jié)構(gòu)體
定義一個結(jié)構(gòu)體的一般形式為:
struct 結(jié)構(gòu)名 { 成員列表 };
成員列表由若干個成員組成,每個成員都是該結(jié)構(gòu)體的一個組成部分。對每個成員也必須作類型說明,其形式:
類型說明符 成員名;//比如:int num;
結(jié)合上面的說明,我們可以構(gòu)建一個簡單的結(jié)構(gòu)體例子:
struct sutdent { int num; char name[20]; //20個字節(jié)長的字符 char sex; int age; float score; char addr[30]; //30個字節(jié)長的字符 }
而如果我們想定義結(jié)構(gòu)體變量,那么我們在定義這個結(jié)構(gòu)體的時候直接定義,或者定義完結(jié)構(gòu)體再另外定義結(jié)構(gòu)體變量,比如:
struct sutdent { int num; char name[20]; //20個字節(jié)長的字符 char sex; int age; float score; char addr[30]; //30個字節(jié)長的字符 }student01,student02; //變量名表列(如果由結(jié)構(gòu)體變量名,那么我們可以不寫結(jié)構(gòu)體名稱)
有時候我們可能需要用到結(jié)構(gòu)體的嵌套,比如:
struct date { int year, month,day; }; struct sutdent { int num; char name[20]; //20個字節(jié)長的字符 char sex; struct date birthday; //這里就用到了結(jié)構(gòu)體的嵌套 int age; float score; char addr[30]; //30個字節(jié)長的字符 }student01,student02; //變量名表列(如果由結(jié)構(gòu)體變量名,那么我們可以不寫結(jié)構(gòu)體名稱)
如果需要引用結(jié)構(gòu)體里面的成員內(nèi)容,可以使用下面的方式:
student01.name = 小李; // 結(jié)構(gòu)體變量名.成員名(注意這里用的是點),這里是對這個成員的賦值
結(jié)構(gòu)指針變量說明的一般形式為:
struct 結(jié)構(gòu)名 *結(jié)構(gòu)指針變量名
假如說我們想定義一個指向結(jié)構(gòu)體"student"的指針變量pstu,那么我們可以使用如下代碼:
struct student *pstu;
如果我們要給一個結(jié)構(gòu)體指針變量賦初值,那么我們可以使用如下的方式:
struct student { char name[66]; int num; char sex; }stu; pstu = &stu;
注意上邊的賦值方式,我們?nèi)绻M(jìn)行賦值,那必須使用結(jié)構(gòu)體變量,而不能使用結(jié)構(gòu)體名,像下邊這樣就是錯誤的。
struct student { char name[66]; int num; char sex; }stu; pstu = &student;
這是因為結(jié)構(gòu)名和結(jié)構(gòu)體變量是兩個不同的概念,結(jié)構(gòu)名只能表示一個結(jié)構(gòu)形式,編譯系統(tǒng)并不會給它分配內(nèi)存空間(就是說不會給它分配地址),而結(jié)構(gòu)體變量作為一個變量,編譯系統(tǒng)會給它分配一個內(nèi)存空間來存儲。
訪問結(jié)構(gòu)體成員的一般形式:
(*pstu).name; //(1)(*結(jié)構(gòu)指針變量).成員名; pstu->name; //(2)結(jié)構(gòu)指針變量->成員名
結(jié)構(gòu)體的知識就簡單說上邊這些。
6 typedef類型別名
typedef用來為現(xiàn)有類型創(chuàng)建一個新的名字,或者稱為類型別名,用來簡化變量的定義(上邊extern變量申明的例子中,"u16"就是對"uint16_t"類型名稱的簡化)。typedef在MDK中用得最多的就是定義結(jié)構(gòu)體的類型別名和枚舉類型。
我們定義一個結(jié)構(gòu)體GPIO:
struct _GPIO { _IO uint32_t MODER; _IO uint32_tOTYPER; ... };
定義這樣一個結(jié)構(gòu)體以后,如果我們想定義一個結(jié)構(gòu)體變量比如"GPIOA",那么我們需要使用這樣的代碼:
struct _GPIO GPIOA;
雖然也可以達(dá)到我們的目的,但是這樣會比較麻煩,而且在MDK中會有很多地方用到,所以,我們可以使用"typedef"為其定義一個別名,這樣直接通過這個別名就可以定義結(jié)構(gòu)體變量,來達(dá)到我們的目的:
typedef struct { _IO uint32_t MODER; _IO uint32_t OTYPER; }GPIO_typedef;
這樣定義完成之后,如果我們需要定義結(jié)構(gòu)體變量,那么我們只需要這樣:
GPIO_typedef _GPIOA,_GPIOB;
編輯:黃飛
?
評論
查看更多