一、前言
相信對(duì)于學(xué)習(xí)過(guò)單片機(jī)的同學(xué)對(duì)于調(diào)庫(kù)這個(gè)操作都不陌生,大家都是從調(diào)別人的庫(kù)階段過(guò)來(lái)的,今天看到一個(gè)評(píng)論說(shuō)如果只會(huì)調(diào)庫(kù)到了公司后會(huì)發(fā)現(xiàn)自己啥都不是,其實(shí)這話(huà)說(shuō)的一點(diǎn)也不假,如果你只會(huì)調(diào)庫(kù)的話(huà)你的單片機(jī)水平還停留在C語(yǔ)言階段,并不能稱(chēng)為真正的單片機(jī)開(kāi)發(fā)。
但是我們要有這么一個(gè)概念,調(diào)庫(kù)是自己編寫(xiě)的開(kāi)始,如果上來(lái)就給你講寄存器這些我相信很多初學(xué)者都接收不了,理解不了這寫(xiě)寄存器到底在干啥,但是如果我們從調(diào)被人庫(kù)開(kāi)始學(xué)習(xí)單片機(jī)我們就會(huì)對(duì)單片機(jī)有個(gè)初始概念,對(duì)于后面的學(xué)習(xí)非常有幫助,今天我們就看一下我們?nèi)绾螐恼{(diào)庫(kù)工程師成為真正的開(kāi)發(fā)工程師。
二、什么是調(diào)庫(kù)?
如果你通過(guò)機(jī)構(gòu)的培訓(xùn)視頻,比如野火的STM32單片機(jī)開(kāi)發(fā)視頻,相信你對(duì)于調(diào)庫(kù)并不陌生,調(diào)庫(kù)其實(shí)就是通過(guò)調(diào)用別人封裝好的庫(kù)函數(shù),來(lái)實(shí)現(xiàn)自己的某些功能,不同的機(jī)構(gòu)封裝出來(lái)的庫(kù)函數(shù)也有所不同,但是基本操作都大同小異,下面我們就以STM32調(diào)用固件庫(kù)實(shí)現(xiàn)點(diǎn)燈為例給大家進(jìn)行講解。
我們先來(lái)看一個(gè)我們非常熟悉的結(jié)構(gòu)體:
?
void?LED_GPIO_Config(void)//初始化相關(guān)的GPIO?第2個(gè)燈 { ?GPIO_InitTypeDef?GPIO_InitStruct; ?/*第一步:打開(kāi)外設(shè)的時(shí)鐘(RCC寄存器控制)*/ ?RCC_APB2PeriphClockCmd(LED1_GPIO_CLK|LED2_GPIO_CLK,ENABLE); ? ?/*第二步:配置外設(shè)初始化結(jié)構(gòu)體*/ ?GPIO_InitStruct.GPIO_Pin?=?LED1_GPIO_PIN; ?GPIO_InitStruct.GPIO_Mode?=?GPIO_Mode_Out_PP;//推挽輸出 ?GPIO_InitStruct.GPIO_Speed?=?GPIO_Speed_10MHz; ? ?/*第三步:調(diào)用外設(shè)初始化函數(shù),把配置好的結(jié)構(gòu)體成員寫(xiě)到寄存器里面*/ ?GPIO_Init(LED1_GPIO_PORT,&GPIO_InitStruct); ? ?GPIO_InitStruct.GPIO_Pin?=?LED2_GPIO_PIN; ?GPIO_Init(LED2_GPIO_PORT,&GPIO_InitStruct); }
?
相信對(duì)于學(xué)習(xí)過(guò)STM32單片機(jī)的同學(xué)對(duì)于這個(gè)函數(shù)都不陌生,這個(gè)函數(shù)其實(shí)就是實(shí)現(xiàn)了對(duì)于一個(gè)GPIO的初始化,相信初學(xué)者并沒(méi)有思考過(guò)我們?yōu)槭裁匆@么初始化呢?這里面的一些函數(shù)都有什么作用呢?他們是在哪個(gè)地方被封裝的呢?我們可不可以不按照這個(gè)函數(shù)的結(jié)構(gòu)來(lái)寫(xiě)呢?
帶著這些一文我們繼續(xù)往更深的層次去探索一下這些東西都是什么意思:
這里面用到了很多的宏定義,我們可以使用右鍵-->go to來(lái)向前查詢(xún)?cè)摵甓x在哪個(gè)地方進(jìn)行定義的,例如我們對(duì)時(shí)鐘的宏定義LED1_GPIO_CLK 具體如下:
?
#define?LED1_GPIO_CLK?????????RCC_APB2Periph_GPIOC//時(shí)鐘 #define?LED1_GPIO_PORT????????GPIOC???????????????//端口 #define?LED1_GPIO_PIN?????????GPIO_Pin_2//pin?引腳 ? #define?LED2_GPIO_CLK?????????RCC_APB2Periph_GPIOC//時(shí)鐘 #define?LED2_GPIO_PORT????????GPIOC???????????????//端口 #define?LED2_GPIO_PIN?????????GPIO_Pin_3//pin
?
我們可以看到一些宏定義,例如LED1_GPIO_CLK被宏定義為RCC_APB2Periph_GPIOC,這里的RCC_APB2Periph_GPIOC就是官方固件庫(kù)中定義的時(shí)鐘,如果你想繼續(xù)研究RCC_APB2Periph_GPIOC代表什么意思,我們可以繼續(xù)右鍵-->go to
我們發(fā)現(xiàn)依然是宏定義,這里將RCC_APB2Periph_GPIOC宏定義成了((uint32_t)0x00000010),如果你想繼續(xù)了解((uint32_t)0x00000010)代表什么的話(huà)那就需要查看STM32的芯片手冊(cè)了,我們這里做一下簡(jiǎn)單的講解。
關(guān)于GPIO的需要用到的寄存器如下:
我們將0x10轉(zhuǎn)換為2進(jìn)制為:1 0000我們可以看到第四位為1,其他位為0,查看芯片手冊(cè)可以發(fā)現(xiàn)第四位解釋如下:
發(fā)現(xiàn)這句話(huà)其實(shí)就是在使能I/O端時(shí)鐘C,和我們的使用是相同的。到這里我們就知道了從封裝的庫(kù)到底層寄存器中間經(jīng)過(guò)了什么,當(dāng)然這只是一個(gè)簡(jiǎn)單的例子,實(shí)際會(huì)比此復(fù)雜很多。
三、如何不調(diào)庫(kù)點(diǎn)亮一個(gè)LED
通過(guò)固件庫(kù)我們可以看到如果想要控制一個(gè)GPIO大概需要以下幾步操作:
打開(kāi)GOIO端口的時(shí)鐘
.配置IO口為輸出(控制CRL寄存器)
配置ODR寄存器
知道了我們需要進(jìn)行的操作下一步我們就可以開(kāi)始通過(guò)寄存器操作來(lái)控制一個(gè)LED了,具體代碼我直接給大家貼出來(lái)大家可以自己進(jìn)行分析。
?
#define?rRCCAHB1CLKEN???*((volatile?unsigned?long?*)0x40023830)?
?
#define?rGPIOF_MODER??*((volatile?unsigned?long?*)0x40021400)???
#define?rGPIOF_OTYPER?*((volatile?unsigned?long?*)0x40021404)?
#define?rGPIOF_OSPEEDR??*((volatile?unsigned?long?*)0x40021408)?
#define?rGPIOF_IDR??*((volatile?unsigned?long?*)0x40021410)?
#define?rGPIOF_ODR??*((volatile?unsigned?long?*)0x40021414)?
?
?
#define?rGPIOE_MODER??*((volatile?unsigned?long?*)0x40021000)
#define?rGPIOE_OTYPER?*((volatile?unsigned?long?*)0x40021004)
#define?rGPIOE_OSPEEDR??*((volatile?unsigned?long?*)0x40021008)
#define?rGPIOE_IDR??*((volatile?unsigned?long?*)0x40021010)
#define?rGPIOE_ODR??*((volatile?unsigned?long?*)0x40021014)
?
?
#define?rGPIOA_MODER??*((volatile?unsigned?long?*)0x40020000)
#define?rGPIOA_OTYPER?*((volatile?unsigned?long?*)0x40020004)
#define?rGPIOA_OSPEEDR??*((volatile?unsigned?long?*)0x40020008)
#define?rGPIOA_IDR??*((volatile?unsigned?long?*)0x40020010)
#define?rGPIOA_ODR??*((volatile?unsigned?long?*)0x40020014)
void?key_init()
{
?
?
?
?rRCCAHB1CLKEN?|=?1?|?(1?<1);
?
?
?
?rGPIOA_MODER&=~(1|(1<<1));
?
?rGPIOF_OSPEEDR?&=?~(1?|?(1?<1)?);
?
?
?
?rGPIOE_MODER&=?~(0x3f<<4);
?
??rGPIOE_MODER?&=?~(0x3f<<4);
}
void?led_init()
{
?
?rRCCAHB1CLKEN?|=?(1?<5)?|?(1?<4);
?
?
?rGPIOF_MODER?&=?~((0x3?<18)?|?(0x3?<20));
?rGPIOF_MODER?|=?(1?<18)?|?(1?<20);??
?
??
?rGPIOF_OTYPER?&=?~(?(1?<9)?|?(1?<10));
?
?
?
?rGPIOF_OSPEEDR?&=?~((0x3?<18)?|?(0x3?<20)?);
?
?rGPIOF_ODR??|=??(1?<9?|?1?<10)?;
?
?
?
?rGPIOE_MODER?&=?~((0X3?<26)?|?(0X3?<28)?);
?rGPIOE_MODER?|=?(1?<26)?|?(1?<28);
?
?rGPIOE_OTYPER?&=?~(?(1?<13)?|?(1?<14));
?
?rGPIOE_OSPEEDR?&=?~((0x3?<26)?|?(0x3?<28)?);
?
?rGPIOE_ODR??|=??(1?<13?|?1?<14)?;
?
?
}
?
?
void?delay(int?i)
{
?int?v?=?i;
?while(v--);
}
?
void?led_on(int?i)
{
?if?(i?==?0)
?{
??rGPIOF_ODR?&=?~(1?<9);
??rGPIOF_ODR?|=?1?<10;
?
??rGPIOE_ODR?|=?(1?<13)?|?(1?<14);
?}
?else?if?(i?==?1)
?{
??rGPIOF_ODR?|=?(1?<9);
??rGPIOF_ODR?&=?~(1?<10);
?
??rGPIOE_ODR?|=?(1?<13)?|?(1?<14);
??
?}
?else?if?(i?==?2)
?{
??rGPIOF_ODR?|=?(1?<9)?|?(1?<10);
?
??rGPIOE_ODR?&=?~(1?<13);
??rGPIOE_ODR?|=?1?<14;
?}
?else?if?(i?==?3)
?{
??rGPIOF_ODR?|=?(1?<9)?|?(1?<10);
?
??rGPIOE_ODR?&=?~(1?<14);
??rGPIOE_ODR?|=?1?<13;
?}
}
?
int?main()
{
?int?i?=?0;
?led_init();
?key_init();
?while(1)
?{
??
????if(!(rGPIOA_IDR&1))
???????{
?????delay(50);//消抖
?????if(!(rGPIOA_IDR&1))
?????{
??????led_on(0);
?????}
????}
????else
????{
????rGPIOF_ODR?|=?1?<9;//μ??e
????}
???if(!(rGPIOE_IDR&(1<<2)))
???????{
?????delay(50);
?????if(!(rGPIOE_IDR&(1<<2)))
?????{
?????led_on(1);
?????}
????}
????else
????{
????rGPIOF_ODR?|=?1?<10;
????}
????if(!(rGPIOE_IDR&(1<<3)))
???????{
?????delay(50);
?????if(!(rGPIOE_IDR&(1<<3)))
?????{
?????led_on(2);
?????}
????}
????else
????{
????rGPIOE_ODR?|=?1?<13;
????}
????if(!(rGPIOE_IDR&(1<<4)))
???????{
?????delay(50);
?????if(!(rGPIOE_IDR&(1<<4)))
?????{
?????led_on(3);
?????}
????}
????else
????{
????rGPIOE_ODR?|=?1?<14;
????}
???
?}
}
?
上面的代碼實(shí)現(xiàn)的功能是通過(guò)循環(huán)掃描判斷按鍵是否被按下,如果按鍵被按下則對(duì)LED引腳輸出低電平從而點(diǎn)亮LED燈,這里用了四個(gè)按鍵和四個(gè)LED,方便大家理解之間的不同,引腳的定義如下:
?
LED的引腳定義為: LED0?->PF9 LED1?->?PF10 LED2->?PE13 LED3?->?PE14 按鍵引腳定義為: KEY0-->?PA0 KEY1-->?PE2 KEY2-->?PE3 KEY3-->?PE4
?
具體每個(gè)寄存器代表什么意思大家可以查看STM32的官方手冊(cè),里面有詳細(xì)的介紹。沒(méi)有手冊(cè)的話(huà)可以看下面這篇文章,里面有常用的寄存器:https://www.cnblogs.com/jzcn/p/15775328.html
四、調(diào)庫(kù)與不調(diào)庫(kù)的區(qū)別
說(shuō)到這兩者的區(qū)別也是我寫(xiě)這篇文章的主要意圖,相信你打開(kāi)這篇文章絕對(duì)不是來(lái)看不調(diào)庫(kù)是如何開(kāi)發(fā)的,而是來(lái)看調(diào)庫(kù)開(kāi)發(fā)和不調(diào)庫(kù)開(kāi)發(fā)具體有哪些區(qū)別,為什么有現(xiàn)成的庫(kù)不用,非要自己去查寄存器,自己進(jìn)行開(kāi)發(fā)。
從應(yīng)用角度講,寄存器相對(duì)來(lái)說(shuō)是屬于更底層的,類(lèi)似于驅(qū)動(dòng)層,而固件庫(kù)則類(lèi)似通過(guò)將寄存器封裝之后的應(yīng)用層。相比之下,固件庫(kù)更像是包裝好給用戶(hù)的產(chǎn)品一樣,只需要我們使用就行了,讓封裝自己和寄存器打交道,而使用寄存器在使用時(shí)必須要清楚自己要操作那個(gè)一個(gè)寄存器,就很復(fù)雜,需要了解清楚寄存器的底層配置。
如果你學(xué)習(xí)過(guò)Linux的話(huà)想必你對(duì)分層的思想是有所了解的,雖然在單片機(jī)中分層思想的應(yīng)用和Linux中的分層不太一樣,但也都是大同小異的。
STM32標(biāo)準(zhǔn)外設(shè)庫(kù)之前的版本也稱(chēng)固件函數(shù)庫(kù)或簡(jiǎn)稱(chēng)固件庫(kù),是?個(gè)固件函數(shù)包,它由程序、數(shù)據(jù)結(jié)構(gòu)和宏組成,包括了微控制器所有外設(shè)的性能特征。
該函數(shù)庫(kù)還包括每?個(gè)外設(shè)的驅(qū)動(dòng)描述和應(yīng)用實(shí)例,為開(kāi)發(fā)者訪(fǎng)問(wèn)底層硬件提供了?個(gè)中間API,通過(guò)使用固件函數(shù)庫(kù),無(wú)需深入掌握底層硬件細(xì)節(jié),開(kāi)發(fā)者就可以輕松應(yīng)?每?個(gè)外設(shè)。
因此,使?固態(tài)函數(shù)庫(kù)可以大大減少用戶(hù)的程序編寫(xiě)時(shí)間,進(jìn)而降低開(kāi)發(fā)成本。每個(gè)外設(shè)驅(qū)動(dòng)都由?組函數(shù)組成,這組函數(shù)覆蓋了該外設(shè)所有功能。每個(gè)器件的開(kāi)發(fā)都由?個(gè)通?API驅(qū)動(dòng),API對(duì)該驅(qū)動(dòng)程序的結(jié)構(gòu),函數(shù)和參數(shù)名稱(chēng)都進(jìn)?了標(biāo)準(zhǔn)化。
這樣的操作既有好處又有壞處,對(duì)于毫無(wú)基礎(chǔ)的人來(lái)說(shuō)它可以使我們的控制更加簡(jiǎn)單,上手更容易,但是他也會(huì)造成我們接觸不到單片機(jī)的底層操作,可能你使用單片機(jī)干過(guò)很多的事,做過(guò)很多的項(xiàng)目,但是對(duì)于單片機(jī)的運(yùn)行邏輯依然不清楚。
從專(zhuān)業(yè)角度來(lái)講,由于寄存器更底層,更需要用戶(hù)了解基本構(gòu)成以及底層配置,所以說(shuō)操作寄存器相對(duì)于固件庫(kù)顯得更加專(zhuān)業(yè),相比之下,直接操作固件庫(kù)不需要了解那么多甚至不了解就可以直接開(kāi)發(fā),并不需要太多專(zhuān)業(yè)知識(shí)。
通過(guò)上面的分析我們可以總結(jié)出他們的優(yōu)缺點(diǎn):固件庫(kù)優(yōu)點(diǎn): 可以直接應(yīng)用,操作更方便,開(kāi)發(fā)迅速,適合新手入門(mén)。固件庫(kù)缺點(diǎn): 因?yàn)椴僮鞴碳?kù),本質(zhì)上也會(huì)對(duì)寄存器的操作,因?yàn)橐ㄟ^(guò)封裝這一中間商,所以執(zhí)行速度要比直接操作寄存器更慢,但是沒(méi)有寄存器移植那么方便。
所以我們可以從固件庫(kù)入門(mén),之后再慢慢深入了解寄存器,了解相關(guān)知識(shí),在我看來(lái),了解更多底層的東西是有利無(wú)害的,更利于提升自己,可以懶,但是不能不會(huì)。
五、為什么要操作寄存器
回歸我們的中心,講了這么多我們到底該如何學(xué)習(xí)單片機(jī)呢?詳細(xì)這個(gè)問(wèn)題在互聯(lián)網(wǎng)上都已經(jīng)被談爛了。對(duì)于初學(xué)者應(yīng)該如何入門(mén)應(yīng)該學(xué)習(xí)哪些東西今天這篇文章我就不再討論了,今天要討論的內(nèi)容是如果你已經(jīng)入門(mén)了,也已經(jīng)通過(guò)操作固件庫(kù)做了很多的東西,下一步你應(yīng)該學(xué)習(xí)哪些東西。
如果你已經(jīng)使用單片機(jī)做了很多的實(shí)驗(yàn),比如什么ADC采集、PWM波輸出這些操作你都用過(guò)了,并且感覺(jué)單片機(jī)你已經(jīng)玩的爐火純青了,那么下面的東西對(duì)你應(yīng)該很有用。
還有一點(diǎn)需要強(qiáng)調(diào)一下,如果未來(lái)你并不打算做單片機(jī)相關(guān)的工作的話(huà)那下面的東西你可以量力而行,可以作為了解的內(nèi)容,并不用深入的了解。
大家學(xué)習(xí)51單片機(jī)的時(shí)候是不是常常進(jìn)行一些寄存器操作,那為什么我們?cè)?2中就很少見(jiàn)到這些直接對(duì)寄存器進(jìn)行操作呢?那是因?yàn)?2的寄存器相比于51單片機(jī)要復(fù)雜很多,比如一個(gè)GPIO的操作可能就和很多的寄存器有關(guān),我們很難通過(guò)一句話(huà)就可以控制一個(gè)GPIO,當(dāng)然不這么干不代表不能這么干。
如果你接觸到單片機(jī)的高級(jí)開(kāi)發(fā)(當(dāng)然沒(méi)有這么一說(shuō),你可以理解成用單片機(jī)做一些產(chǎn)品)那么你的開(kāi)發(fā)就會(huì)遇到瓶頸,從而限制你的開(kāi)發(fā),這也是很多單片機(jī)開(kāi)發(fā)要求你一定要會(huì)寄存器操作。
對(duì)于經(jīng)過(guò)系統(tǒng)培訓(xùn)的開(kāi)發(fā)者,單片機(jī)(MCU)或SoC的驅(qū)動(dòng)開(kāi)發(fā),不管是使用各種庫(kù)還是直接上寄存器,都不成問(wèn)題。
HAL庫(kù)函數(shù)或者固件庫(kù)都是ST開(kāi)發(fā)的,也是人寫(xiě)出來(lái)的代碼,既然是代碼,那就有存在BUG的可能,而且像這些經(jīng)過(guò)ST調(diào)試過(guò)的代碼,更可能隱藏深層問(wèn)題,這些都需要通過(guò)修改寄存器配置來(lái)調(diào)試定位。
所以這就可能在你的代碼里埋下了更深的炸彈,而且這些炸彈是埋藏的非常深的(一般的小bug是不會(huì)有的,畢竟那么多人使用)而這些bug一旦復(fù)現(xiàn),你就會(huì)不知所措,完全不知道從何查起。
而且一般公司的MCU都不是你平時(shí)學(xué)的這些單片機(jī),而是一些工業(yè)級(jí)的MCU,所以你可以想一想如果你一直使用的都是STM32的固件庫(kù)進(jìn)行的開(kāi)發(fā),從來(lái)沒(méi)接觸過(guò)寄存器操作,或者根本都不知道怎么看芯片手冊(cè),怎么操作寄存器,那么你怎么保證你們公司使用的MCU你就一定會(huì)操作呢?
所以對(duì)于STM32或者51這類(lèi)單片機(jī)的定位你就把它當(dāng)成學(xué)習(xí)使用的,你要通過(guò)這類(lèi)簡(jiǎn)單的、有豐富資料的單片機(jī)去入門(mén),去學(xué)習(xí)。當(dāng)你的學(xué)習(xí)內(nèi)容達(dá)到一定程度之后就一定會(huì)接觸到寄存器這些操作,說(shuō)實(shí)話(huà)寄存器操作也只是工作的基礎(chǔ),最重要的是舉一反三,通過(guò)一個(gè)單片機(jī)學(xué)習(xí)到所有MCU操作的本質(zhì),這樣才能更好的在工作中使用,而不受單片機(jī)(MCU)型號(hào)的限制。
六、結(jié)語(yǔ)
對(duì)于單片機(jī)的學(xué)習(xí)我們可以使用單片機(jī)的固件庫(kù)入門(mén),初步了解單片機(jī)操作的步驟,可以先不接觸寄存器,等到固件庫(kù)使用的非常熟悉之后可以轉(zhuǎn)戰(zhàn)寄存器了。
對(duì)于寄存器操作絕不是點(diǎn)個(gè)小燈就完了,你需要做的是知道如何查看芯片手冊(cè),知道固件庫(kù)里的每個(gè)宏定義或者函數(shù)這么寫(xiě)的依據(jù)是什么?如果讓你來(lái)寫(xiě)一個(gè)固件庫(kù)你會(huì)怎么寫(xiě)?
當(dāng)你的水平能夠達(dá)到對(duì)STM32的寄存器操作已經(jīng)非常6的話(huà)你可以嘗試幾款工業(yè)級(jí)的MCU,例如工業(yè)非常常用的TC397,這個(gè)MCU在車(chē)載行業(yè)用的非常多,可以嘗試一下,不過(guò)治療可能不太好找,如果遇到問(wèn)題的話(huà)就需要自己琢磨了,這也是一種進(jìn)步。
審核編輯:湯梓紅
電子發(fā)燒友App




評(píng)論