??在上一節的基礎上,進一步改寫代碼,再引入官方標注庫函數。雖然官方標準庫慢慢式微,有一些別的庫可能會取代它,但是并不妨礙我們繼續拿官方庫來寫代碼,吸取里邊好的寫法,強化下C語言技能,加深對寄存器的理解也是不錯的。
本文模仿庫函數,首先自定義庫函數,然后一步一步改寫代碼,最終引入官方標準庫函數。
實現流水燈
void delay(unsigned int a)
{
while(a--);
}
int main(void)
{
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
GPIOB->CRH &= ~(0xf<<(0*4)|0xf<<(1*4));
GPIOB->CRH |= 0x3<<(0*4)|0x3<<(1*4);
while(1)
{
GPIOB->ODR &= ~(1<<8); //PB8 = 0
GPIOB->ODR |= 1<<9; //PB9 = 1
delay(0xfffff);
GPIOB->ODR &= ~(1<<9); //PB9 = 0
GPIOB->ODR |= 1<<8; //PB8 = 1
delay(0xfffff);
}
}
??主要是增加了延時函數與while(1)的循環。通過寄存器,實現流水燈。這是單片機最基礎的實驗,意義等同于helloworld。這些代碼如果有51基礎,是很好理解的。但是存在問題:代碼的擴展性與維護性較差,存在較多的復制粘貼,不太容易看懂,如果出現業務變更,如LED的引腳換了,那么修改工作太大。
??接下來我們來自己從零開始,寫一個庫函數,目的是提高代碼的擴展性和可維護性,具體表現是:
1方便移植,比較通用。
2讀起來沒那么費勁,不用翻著手冊來讀。
自定義IO初始化函數
??在不考慮時鐘的情況下,配置一個IO口(也就是引腳)需要知道以下信息:
??1 PORT PA還是PB
??2 PIN PB1還是PB2
??3 模式 輸入還是輸出?
??我們可以把這幾個信息作為函數的參數,用一個函數來進行IO的初始化。例如初始化PB0位2Mhz的推挽輸出:
void easy_IO(GPIO_TypeDef *GPIOx,char pin,char cny,char mode)
{
uint8_t temp;
if(mode != shuru)
{
GPIOx->CRL &= ~(0xf << pin*4);
temp =(cny<<2)|(mode);
GPIOx ->CRL |= temp << (pin*4);
}
}
easy_IO(GPIOB,0,tuiwan,MHZ2);//調用
??用這種方法可以比較方便地進行引腳的初始化,弊端在于,參數太多,容易出錯。有沒有更好的傳參數的方法?
使用結構體
??首先把需要的信息都放在一個結構體內。
typedef struct
{
Uint16_t pin;
uint8_t mode;//輸入還是輸出,速度
uint8_t cny;//輸出模式
}GPIO_InitST;
??然后定義兩個枚舉類型,為參數可能的取值起一個好理解的名字:
typedef enum
{
shuru = 0x00,
MHZ10,
MHZ2,
MHZ50,
}ModeEm;
typedef enum
{
tuiwan = 0x00,
kailou,
futuiwan,
fukailou,
}CNYEm;
??最后定義一個函數,用于IO的初始化:
//自定義IO初始化函數
void myGPIO_Init(GPIO_TypeDef* GPIOx,GPIO_InitST* st)
{
if(st->mode != shuru)//暫時先只處理輸出的情況
{
uint8_t temp;
if(st->pin<8)//P0-P7用CRL
{
GPIOx->CRL &= ~(0xf< temp = (st->cny<<2)|(st->mode); GPIOx->CRL |= temp< } else//P8及以上是CRH { GPIOx->CRH &= ~(0xf<<(st->pin-8)*4); temp = (st->cny<<2)|(st->mode); GPIOx->CRH |= temp<<(st->pin-8)*4; } } } ??主函數中IO的初始化可以直接調用函數: int main(void) { GPIO_InitST myst; RCC->APB2ENR |= RCC_APB2ENR_IOPBEN; myst.pin = 8; myst.mode = MHZ50; myst.cny = tuiwan; myGPIO_Init(GPIOB,&myst); myst.pin = 9; myGPIO_Init(GPIOB,&myst); while(1) { GPIOB->ODR &= ~(1<<8); //PB8 = 0 GPIOB->ODR |= 1<<9; //PB9 = 1 delay(0xfffff); GPIOB->ODR &= ~(1<<9); //PB9 = 0 GPIOB->ODR |= 1<<8; //PB8 = 1 delay(0xfffff); } } ??編譯程序,下載觀察現象,跟流水燈一樣。 使用獨熱碼操作多個引腳 ??有兩個不同引腳的話,需要調用兩次初始化函數,能不能調用一次初始化的函數就初始化兩個引腳?可以,代碼仍然有改進的空間。 ??使用獨熱碼one-hot code, 直觀來說就是有多少個狀態就有多少比特,而且只有一個比特為1,其他全為0的一種碼制。 ??如果引腳0定義為0x01,引腳1定義為0x02,引腳2定義為0x04(而不是0x03),那么0x07就可以代表這三個引腳。想同時初始化三個引腳,只需要傳入一個參數,0x07。 //自定義IO初始化函數 void myGPIO_Init(GPIO_TypeDef* GPIOx,GPIO_InitST* st) { uint16_t i=0, j = 0, pflg = 0; uint8_t temp; if(st->mode != shuru)//暫時先只處理輸出的情況 { for(i = 0 ; i < 16 ; i++) { j = 0x01 << i; pflg = st->pin & j; if (pflg == j) { if(i<8)//P0-P7用CRL { GPIOx->CRL &= ~(0xf< temp = (st->cny<<2)|(st->mode); GPIOx->CRL |= temp< } else//P8及以上是CRH { GPIOx->CRH &= ~(0xf<<(i-8)*4); temp = (st->cny<<2)|(st->mode); GPIOx->CRH |= temp<<(i-8)*4; } } } } } ??以上代碼,主要思想是通過一個for循環,將傳入的參數按位取出,并判斷這一位是0還是1。如果是1,則需要對這一位對應的IO進行操作。 ??然后主函數的調用也需要做相應的修改,一次傳入PB8與PB9兩個引腳。 #define Pin_8 ((uint16_t)0x0100) /*!< Pin 8 selected */ #define Pin_9 ((uint16_t)0x0200) /*!< Pin 9 selected */ int main(void) { GPIO_InitST myst; RCC->APB2ENR |= RCC_APB2ENR_IOPBEN; myst.pin = Pin_8| Pin_9; myst.mode = MHZ50; myst.cny = tuiwan; myGPIO_Init(GPIOB,&myst); while(1) { GPIOB->ODR &= ~(1<<8); //PB8 = 0 GPIOB->ODR |= 1<<9; //PB9 = 1 delay(0xfffff); GPIOB->ODR &= ~(1<<9); //PB9 = 0 GPIOB->ODR |= 1<<8; //PB8 = 1 delay(0xfffff); } } 改寫引腳操作 ??在使用ODR寄存器的時候,總是需要關注那些不應該被操作的引腳,怕誤操作。現在由51單片機的思維改為STM32的思維。STM32提供了特別豐富,好用的寄存器,例如BSRR(端口位設置/清除)寄存器,對某個位寫1代表把對應的IO設置為高電平,其它位寫0,則不對其它位產生影響。 ??看數據手冊 ??跟它類似的有個BRR寄存器,功能是端口位清零。 ??借助這兩個寄存器,我們可以很方便的寫出IO設置為1和IO設置為0的兩個函數: void mySetbits(GPIO_TypeDef* GPIOx,uint16_t pin)//引腳設置1 { GPIOx->BSRR = pin; } void myResetbits(GPIO_TypeDef* GPIOx, uint16_t pin)//引腳設置0 { GPIOx-> BRR= pin; } ??然后修改主函數內的死循環: while(1) { myResetbits(GPIOB,Pin_8); //PB8 = 0 mySetbits(GPIOB,Pin_9); //PB9 = 1 delay(0xfffff); myResetbits(GPIOB,Pin_9); //PB9 = 0 mySetbits(GPIOB,Pin_8); //PB8 = 1 delay(0xfffff); } ??最終效果還是一樣的,但代碼看上去就比較爽心悅目了。事實上,這已經很接近于庫函數的代碼了。 引入官方庫函數 ??官方的固件庫提供了很多好用的函數,接下來由自定義的固件庫轉到官方標準固件庫。 ??官方標準固件庫(以后簡稱官方庫)的函數可以查看文檔《STM32固件庫使用手冊的中文翻譯版.pdf》 ??官方庫除了提供了詳細的函數說明,還提供了使用的例子。另外,通過函數的跳轉,可以看到官方庫的源碼,這些源碼經過千錘百煉,都是學習單片機編程的好榜樣。 ??與引腳相關的函數都在GPIO章節,讀者可以自行查看。新建一個函數,IO_Init()。參考例子,稍作修改就能寫完LED引腳的初始化函數。 void IO_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8|GPIO_Pin_9; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(GPIOB, &GPIO_InitStructure); } int main(void) { IO_Init(); GPIO_SetBits(GPIOB,GPIO_Pin_8|GPIO_Pin_9); while(1) { GPIO_ResetBits(GPIOB,GPIO_Pin_8); //PB8 = 0 GPIO_SetBits(GPIOB,GPIO_Pin_9); //PB9 = 1 delay(0xfffff); GPIO_ResetBits(GPIOB,GPIO_Pin_9); //PB9 = 0 GPIO_SetBits(GPIOB,GPIO_Pin_8); //PB8 = 1 delay(0xfffff); } } ??我們通過對寄存器的封裝,實現了最簡單的庫函數。使用STM32編程,庫函數遠比寄存器方便。所謂的庫函數,就是封裝寄存器,提供接口給用戶調用。如果是需要快速開發的時候,可以直接使用庫函數,不必糾結于是怎樣實現的,要大膽的拿來就用。但是,學習的時候,還是要考慮下庫函數的實現方式,庫函數是經過千錘百煉的優秀代碼。因此我們花費巨大的精力,自己實現了簡單的庫函數,就是為了讓大家明白,庫函數不神秘,如果需要,我們自己就能寫出來。以后使用其它的平臺,也能根據數據手冊,操作寄存器,實現功能,并把代碼封裝,優化,這才是最好的結果。 ??項目式開發要避免重復造輪子,要盡快完成目標,而不是把時間浪費在不必要的細節上。庫函數與寄存器兩種開發方式并不對立,哪個方便用哪個即可。不同類型的庫也是,那個方便用哪個即可。
上一篇:STM32掌機教程2,掌機的原理
下一篇:最后一頁
推薦閱讀
史海拾趣
Elekon始終將品質放在首位。公司建立了完善的質量管理體系,從原材料采購到生產、檢測、包裝等各個環節都進行嚴格的質量控制。此外,Elekon還引進了先進的生產設備和技術,確保產品的質量和性能達到國際先進水平。這些舉措使得Elekon的產品在市場上享有很高的聲譽和口碑。
ETL始終將品質管理和客戶服務放在首位。公司建立了完善的質量管理體系,從原材料采購到生產、檢測、包裝等各個環節都進行嚴格的質量控制。同時,ETL還注重提升客戶服務水平,為客戶提供全方位、個性化的服務。這些措施使得ETL的產品質量得到了客戶的高度認可,也為公司贏得了良好的口碑。
隨著全球化進程的加速推進,Ceratech Corporation公司意識到要想在激烈的國際競爭中立于不敗之地,必須實施全球化戰略。公司開始積極拓展海外市場,通過建立海外分公司、參加國際展會等方式,加強與國際同行的交流與合作。同時,公司還積極引進國際先進的技術和管理經驗,不斷提升自身的創新能力和管理水平。通過全球化戰略的實施,Ceratech Corporation逐漸成為了國際電子行業中的佼佼者。
DIALIGHT公司的故事始于1938年的紐約布魯克林,當時該公司專注于為飛機生產儀表板燈。隨著技術的不斷進步和市場的變化,公司在1971年,即LED推出僅一年后,推出了他們的第一個LED產品。這一舉措標志著DIALIGHT正式從傳統的飛機儀表板燈制造轉向LED照明技術的研發和應用。從此,DIALIGHT徹底改變了LED的用途,將其廣泛應用于世界各地的交通控制、指示燈、結構塔和工業場所,為全球提供了優質的照明解決方案。
在集成電路設計領域,技術的突破是贏得市場的關鍵。成都華微科技始終堅持以技術研發為核心,不斷投入巨資進行技術研發和創新。經過多年的努力,公司在可編程邏輯器件、系統級芯片、存儲器和模數/數模轉換器(AD/DA)芯片等領域取得了重大突破,產品性能達到了國內領先水平。這些技術突破不僅為公司贏得了市場的認可,也提升了中國集成電路設計行業的國際地位。
CP Technology Inc成立之初,便專注于半導體技術的研發。公司研發團隊成功開發出一種高效能、低功耗的芯片,這一創新為公司的快速崛起奠定了基石。隨著技術的不斷完善和產品的廣泛應用,CP Technology Inc逐漸在半導體市場上占據了一席之地。公司通過持續的技術創新和產品迭代,不斷滿足市場需求,贏得了客戶的信賴和支持。
這個應用筆記是ADI中國技術支持中心的技術人員所做,個人感覺,硬件濾波設計的不錯,而且應用設計文檔制作的是中文版,使人感到通俗易懂。同時CPU采用ARM內核的ADuC7020,因此可移植性比較好,總之,推薦大家,尤其是那些對濾波技術感興趣的朋友, ...… 查看全部問答∨ |
許久以來,一直想說說我的外甥。 說外甥,不僅是因為我是他的舅舅,而是因為他是最讓我想不通的一個人。 認識我以及我的外甥的許多朋友,無不為我的外甥感到異常的可惜和不解。 是的,我們想不通,他究竟為什么要 ...… 查看全部問答∨ |
比如我們的手機以modem方式插入電腦后,會虛擬出很多個串口。 其中包括一般的串口和modem串口。 這是怎么實現的呢,大概原理是什么。 我的理解是:一個USB設備包含多個端點(Endpoint),一般是4個,每個端點用作不同的功能,因此當插入USB主機 ...… 查看全部問答∨ |
|
blackfin 561 PROGRAMMABLE FLAGS 的問題 FIOn_FLAG_D,FIOn_FLAG_S,FIOn_FLAG_C,FIOn_FLAG_T這四個寄存器究竟有什么作用,具體的用法是怎么樣的?看了好久的硬件手冊也沒有明白。哪位仁兄能指點一下啊?… 查看全部問答∨ |
我的T0,T1已經用了,現在要用串口,所以模擬了一個,要用一個定時器,正好我的還有T2,我就是想設置成定時器,因為模擬串口要改定時的值,所以不用自動重裝模式,只能用捕獲了吧。我的設置如下: T2CON = 0X01;//捕獲模式 TL2 = rs_START_BIT_L ...… 查看全部問答∨ |
求linux驅動開發的電子書籍,能給鏈接是最好的,要不可以直接發到我郵箱。jamesf1982@163.com 非常感謝你的支持,對于給予幫助的朋友,每人加50分獎勵… 查看全部問答∨ |