A. 學習MCS-51(具體以89C51為例)系列單片機的體會
http://www.38xian.com/index.aspx?menuid=4&type=articleinfo&lanmuid=19&infoid=685&language=cn
http://www.38xian.com/index.aspx?menuid=4&type=articleinfo&lanmuid=19&infoid=686&language=cn
又來看了一下,修改一下吧,加點文字
C語言學習總結
搞嵌入式的,大都用C語言寫代碼,本人從事單片機開發,也寫了不少的代碼,一直習慣用 if 、switch打天下,在定義數據結構的時候也只用到 字元型、整型、數組,位;很少用結構體,共用體,枚舉,因為咱C語言學得不好,和它們不熟,總感覺它們不那麼好招呼,重要的是自已覺得沒必要用上它們。隨著越來越多的積累,咱寫代碼的風格也在不斷的發生變化,從以前的喜歡將所有的函數及數據的定義寫在一個文件里到逐漸的將函數按功能模塊化、從以前的習慣直接在程序里寫常數到慢慢的開始用上宏來代替,咱編程的風格也逐漸開始正規化,編程水平也逐步提升,當然這些成績都源於咱不斷的學習,學習匠人的編程規范、學習herald的感悟設計、還有網上寫得非常出色的代碼以及STM32的固件函數庫,在咱的不斷領悟和思考下,總結了幾點關於C語言的用法,與大家共同分享。
一、 學習匠人的頭文件包含巧妙用法
當一個頭文件被多個C文件包含,且該頭文件中定義了這些C文件的公共變數,則在編譯的時候會出現重復定義,導致編譯通不過,通常我們會採用如下兩種做法來解決上述問題。
(為了讓問題表述得更清楚,我們假設兩個C文件C1,C2,C3,一個頭文件H1,C1,C2,C3有兩個公共變數V1和V2)
1、 在C1文件中定義變數V1和V2,在C2和C3文件中對V1,V2用extern聲明;
2、 在C1文件中定義變數V1和V2,在H1中對V1,V2用extern聲明,然後在C2和C3文件中包含H1;
很顯然,以上兩種方法都要對V1和V2書寫至少兩次,一次定義,一次外部聲明,且不是在同一文件下,這樣不利於管理和修改,有沒有一種方法可以讓這些公用的變數放在一個文件里,且只要書寫一次呢?
偶在二姨那裡無意中看到匠人的發帖,就是關於該問題的討論,現在我轉發一下,與大家同共分享。首先我們將要用到的公共變數全部書寫到com.h文件中,每一個變數在定義前加一個符號EXT_,當該頭文件被main.c函數包含時,定義EXT_為空,表示com.h中的變數在main.c中被定義,當被其它文件包含時,定義EXT_為extern,表示外部聲明,如:
Com.h文件:
//避免重復定義
#ifdef root
#define EXT_
#else
#define EXT_ extern
#endif
//全局變數
EXT_ u8 variable1; //該變數在三個C文件中都要用到
Main.c
#define root //在包含com.h前定義root
#include "com.h"
二、 用結構體的方式來定義匯流排或外設地址
當一個整體包含不同類型的多個成員時,通常用結構體來定義結構體變數,這樣內存會將這些變數按照遞增的方式分配到相鄰的地址(不對齊的地方會有填充),按「結構體名.成員名」的方式訪問結構體內的成員,這是訪問結構體變數的方式;但是還有一種指向結構體變數的指針,它可以將某個地址轉換成該結構體類型的指針,比如寄存器的定義:
(以下是摘自STM32固件函數庫,關於GPIO的定義)
typedef struct
{
vu32 CRL; //0
vu32 CRH; //偏移量4
vu32 IDR; //偏移量8
vu32 ODR;
vu32 BSRR;
vu32 BRR;
vu32 LCKR;
} GPIO_TypeDef;
#define GPIOA_BASE ((u32)0x40010800) //GPIOA的基地址為0x40010800
#define GPIOA (GPIO_TypeDef *) GPIOA_BASE; //強制類型轉換為GPIO_TypeDef類型的指針
這樣在操作GPIOA的寄存器時只要這樣寫就可以了
讀: X="GPIOA-">CRL; 寫:GPIOA->CRL=X;
或 讀: X=(*GPIOA).CRL; 寫:(*GPIOA).CRL =X;
當然,要達到上述目的也可以採用如下方式
#define GPIOA_ CRL 0x40010800
#define GPIOA_ CRH 0x40010804
#define GPIOA_ IDR 0x40010808
#define GPIOA_ ODR 0x4001080C
#define GPIOA_ BSSR 0x40010810
#define GPIOA_ LCKR 0x40010814
很明顯,第一種書寫方式更加正規化,且當定義多個GPIO時,只要將其它GPIO的基地址強制轉換為該結構類型的指針即可。
再來看看一個定義外部匯流排的例子
typedef struct
{
vu8 CH375_DATA;
vu8 CH375_CMD; //偏移量1
} CH375_TypeDef;
#define CH375 ((CH375_TypeDef *) 0x6c000000)
CH375-> CH375_DATA=data; //往0x6c000000地址處寫數據
CH375-> CH375_CMD=cmd; //往0x6c000001地址處寫命令
怎麼樣,是不是方便多了。重要的是代碼的觀賞和可讀性提高了。
三、 用枚舉數據類型來定義特定的狀態
在實際問題中,有些變數的取值被限定在一個有限的范圍內。例如,一個函數在操作過程中會返回幾個特定的狀態:操作成功,操作失敗,忙,等等。如果我們直接在函數里用0,1和2來表示這三種狀態,有時偶爾會出現數值與實際狀態對不上號的情況,造成置狀態和判斷狀態錯誤,那麼我們可以在程序里用宏或者枚舉來事先定義好這些狀態。
如:用宏定義:
#define Sucess 0
#define Failure 1
#define Busy 2
用枚舉
typedef enum { Sucess = 0, Failure , Busy } FlagStatus;
四、 用共用體類型定義共享內存空間
共用體類型定義的數據是將多個成員共享同一內存空間,該空間的大小為最大成員的大小,其用法與結構體完全相同,但值得注意的是不能同時引用多個成員,在某一時刻只能使用其中之一成員。
在程序中如果全局變數比較多,包含幾個結構和數組,如果這些全部定義的話勢必會佔大量的內存,有可能還會導致單片機內存不夠,如果能讓幾個不同時用到的數組和結構變數共享一段內存,則能省出很多的內存空間。
比如以下輸入輸出若不同時進行,則可以共享同一段內存空間
union {
struct {
unsigned char Flag;
unsigned char Type;
unsigned char State;
unsigned long DataLen;
unsigned char Buffer[64];
}DataOut;
struct {
unsigned char Flag;
unsigned char Type;
unsigned char State;
unsigned long DataLen;
unsigned char Buffer[64];
} DataIn;
} BOC;
C語言博大精深,豐富多彩,用得好能很好的發揮它的作用,同時學習好的編程方法養成良好的編程習慣對於一名設計人員來說也是極其的重要,以上四點都是本人自身積累和學習的一些總結,希望能夠與大家一起共同交流,共同學習和提高。
個人珍藏的好文章,貼出來分享
B. stm8s鍗曠墖鏈鴻籭o鍙g數騫蟲庝箞楗挎搷浣
a=PB_IDR&0x01;
C. STM8S103K3,ST的8位單片機,寄存器的位操作
在IAR裡面先是用了結構體struct的位域定義一個位元組的八位,然後再用聯合體union定義兩個變數,一個unsigned char NAME用於全局操作,一個BIT_STRUCT NAME ## _bit用於位操作。如果還不明白,請先了解結構體與聯合體。
D. stm32 C語言特殊性
這是標準的C語言啊,不是STM32的特殊用法,你那個寫的是對的,相當於:
((int *) 0x00000000) = 1;
這么個意思;
那麼為什麼要用結構體呢?
不是C語言的特殊用法,是因為STM32這種單片機的外設架構,STM32把所有同類型的外設集中到了一塊,這樣庫就好寫多了。
太詳細我也說不明白,給你舉個例子吧:
比如說,我是說比如啊,GPIOA的CRL寄存器地址是0,CRH寄存器地址是4,IDR寄存器地址是8,ODR寄存器地址是12,那麼,按照你剛才的寫法是
#define GPIOA_CRL *((unsigned long*)(0x00000000))
#define GPIOA_CRH *((unsigned long*)(0x00000004))
#define GPIOA_IDR *((unsigned long*)(0x00000008))
#define GPIOA_ODR *((unsigned long*)(0x0000000C))
如果用結構體,就方便多了
typedef struct
{
vu32 CRL;
vu32 CRH;
vu32 IDR;
vu32 ODR;
vu32 BSRR;
vu32 BRR;
vu32 LCKR;
} GPIO_TypeDef;
GPIO_TypeDef *GPIOA = (GPIO_TypeDef *)(0x00000000);
這樣一寫,那麼,就註定了GPIOA->CRL 的地址肯定是0;
GPIOA->CRH的地址肯定是4;
GPIOA->IDR的地址肯定是8;
比你那樣寫省事了不少吧?
E. PIC單片機編程軟體
KEIL沒辦法編譯PIC的任何單片機的程序
用PIC單片機生產商MICROCHIP公司自己推出的MPLAB ide軟體開發程序,免費的。
但這個軟體默認安裝的沒有C編譯器(默認的只能編譯匯編文件)。你還得去Microchip那裡下載PICC for PIC18(HI-TECH公司做的,這公司被MICROCHIP收購了),或者是Microchip公司自己開發MPLAB C18編譯器。這些編譯器安裝後自動嵌入到MPLAB內部。
反正這兩個都是收費的(最便宜的PICC買800rmb)。但網上有很多關於他們的破解版
F. STM32涓嬈℃ц誨彇32浣嶆寜閿鐨勫
uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);//搴撴柟寮
uint16_t KeyH=GPIOx->IDR;//鐩存帴鎿嶄綔瀵勫瓨鍣
璇誨彇鏁翠釜GPIOX鐨勮緭鍏ョ姸鎬併
瑕佹槸闇瑕佽誨叆涓や釜GPIO絝鍙g殑鐘舵佺殑璇濓紝鍏堝垎鍒璇誨彇絝鍙g姸鎬佸瓨鍒板彉閲忎腑錛屼箣鍚庤繘琛屾暟鎹澶勭悊鍗沖彲銆
G. STM8單片機中,DDR .ODR.IDR是什麼呀,51程序中都沒有寫到這些。
DDR是方向寄存器,值為0時IO口輸入,為1時IO口輸出。
ODR是輸出寄存器,當IO口在輸出狀態下時,ODR值為0則輸出低電平,為1輸出高電平。
IDR是輸入寄存器,IO口在輸入模式下,會因外設的狀態改變而產生高低電平,讀取電平的高低可判斷外設的變化;比如計時到一定程度,電平變低(為0),單片機讀取IDR值為0,就知道計時達到某個點了。