大家學習嵌入式的時候,多多學習用KEIL寫代碼,雖然作為編譯器,大家常用vscode等常用工具關聯編碼,但目前keil仍然是主流工具之一,學習掌握十分必要。
1.再次創建項目
1.1編譯器自動生成文件
1.2初始文件
這樣下次創建新項目時,只需復制上一個項目,刪除自動生成的文件,后根據自我需求進行修改。
重新打開工程后,需要重新配置,重新配置后才會生成文件。
2.再做項目
2.1需求
在第一個項目中,我們點亮了黃燈。接下來點亮藍燈和綠燈。
2.2準備工作
2.2.1原理圖查接線
?
由原理圖可知,LED負極端給一個低電平就可以點亮。得出有效條件:低電平有效
2.2.2有效信息寫代碼
我們知道,所有外設都基本掛在系統總線上,APB1? APB2.從總線上找GPIO
他們都屬于GPIOA同一組時鐘。
配置PA1為輸出,看寄存器CRL.PA0是后面的四位,那PA1就是前面的四位。
通用推挽輸出的配置通常需要將 CNF 設置為 00(推挽輸出),而 MODE 根據所需的速度選擇,比如 2MHz、10MHz 或 50MHz。例如,對于 50MHz 的速度,MODE 位應設置為 11。因此,對應的 CRL 位需要設置為 0011,即十進制的 3,也就是0x?30
最后幾位應該是1101,對應16進制D。所以0xfffd。
藍燈就會亮起。黃燈之所以不亮是因為如果沒有延遲,是觀察不到的,它的程序反應速度很快。
綠燈對應引腳PA8。因為8-15的引腳不在低寄存器里,需要看高寄存器。也就是CRH的最低四位
基地址+偏移地址。*(uint32 *)(0x4001?0800+0x04)=0x03
輸出低電平,偏移量0x0c.給ODR8一個0.也就是0xfeff.
這樣綠燈就會亮起。
看到這里,也就明白了,讓三燈同時亮起。
也就是ODR端口數據寄存器配置為0xfefc。
端口配置寄存器CRL,就讓低8位為00110011,其他全是0,也就是0x33.
這就是電燈案例的標準庫寄存器寫法,但是不是有些難看?
寄存器寫法的步驟也無非就是開啟GPIO時鐘,配置端口寄存器輸入輸出,配置輸入輸出數據。
先找到引腳的起始地址,再找寄存器對應偏移量,加一起就可以找到寄存器操作地址,還需要強轉成32位指針。可不可以不需要這些地址?
我們可以選擇優化!
2.3代碼優化
不知道大家發現沒,我們并沒有用到Start里面的.h文件。并沒有引入各種額外文件。
如果簡化的話,就需要引入額外文件。將一些地址加以宏定義,就會好看許多。
2.3.1 改進1 使用宏定義改寫寄存器地址
引入文件
時鐘配置也可以
RCC->APB2ENR=4;
注意stm32f10x.h文件里面集成了很多寄存器和引腳的宏定義。只有引用他才能宏替換地址。
優點:這樣比地址好看很多,只要記住模塊名和寄存器名稱,就能配置。
但是目前缺陷也很明顯。例如RCC->APB2ENR=4,不僅打開了GPIOA時鐘,也將其他的模塊關閉了。我們應該在不影響其他模塊的前提下,打開GPIOA時鐘。
C語言里面講過位運算,只改想要的位,不改其他位。
2.3.2 使用位運算對某一位進行操作
跟0作位于,一定為0;跟1作位或,一定為1;
置0,與0位于,置一,與1位或.
2.3.3 改進2 使用位運算實現只改變某一位的值。
RCC->APB2ENR=4? 4即0x0100,只要讓第三位為1,其他保持不變。那就只要和一個第三位為1,其他為0的數作位或即可。也就是與(1<<2)做位或。
RCC->APB2ENR|=(1<<2)即可。
配置PA0為輸出,GPIOA->CRL=0x03; 0011,cnF0=00,mode=11,前兩位為0,后兩位是1,其他位不受影響。這樣才能保證PA0為輸出。
GPIOA->CRL|=(1<<0);
GPIOA->CRL|=(1<<1);
GPIOA->CRL&=~(1<<3);
GPIOA->CRL&=~(1<<4);
? PA0輸出低電平,ODR最后一位為0,其他位不改變。
GPIOA->ODR&=~(1<<0);
led1 黃燈就會亮。
這種寫代碼方法只需要記住模塊和寄存器,后續還要記住第幾位,需要查手冊。
2.3.4 改進3 使用宏定義改變每一位的表示
時鐘開啟可以改進:RCC->APB2ENR|=(1<<2)
改進后:RCC->APB2ENR|=RCC_APB2ENR_IOPAENR
大家要熟悉這種寫法,以后寄存器寫法的代碼都是這種風格。完全靠宏定義取代了地址的寫法,這個主推!!!
3.GPIO整體概述
GPIO引腳就是通用輸入輸出引腳。存在意義便是用程序控制他們的輸入或輸出。
3.1 與GPIO相關寄存器
3.2 8種工作模式
3.3推挽輸出總結
3.4 開漏輸出
3.5 推挽輸出和開漏輸出的選擇?
使用推挽:
1.驅動能力需求較高的場合
2.高速信號傳輸
3.無需共用信號線的場合
使用開漏:
1.多個設備共用信號線
2.不同電壓系統之間的接口
3.需要外部上拉電阻來確定邏輯高電平的場合?
3.6復用輸出模式
復用輸出信號來自片上外設(芯片中各種外設模塊)
通用開漏,連ODR。
復用開漏,那條線打到片上外設,選哪個模塊,配置哪個
復用功能(AF),端口必須配置成復用輸出。
看下圖,輸入模式下,輸出完全不導通。
浮空輸入,上拉下拉都斷開。還想上下拉的話,外部接上拉電阻或下拉電阻。
總結:
模擬輸入耗電極小,肖特基觸發器關閉。
輸入模式就不用分通用還是復用了,都是外部輸入來的。
4.GPIO寄存器
每組GPIO端口,都有7個相關寄存器。
配置寄存器和數據寄存器幾乎是配置GPIO必用的,必須背會。
上下拉輸入都是10 00,那么就用ODR這一位來區分。下拉ODR配0,上拉ODR配1.
0-7引腳用端口配置低寄存器來配,8-15引腳用端口高寄存器來配。
復位值16進制,每一位都是4,0100,對應浮空輸入,斷掉上下拉電阻的開關,最省電。
輸入數據寄存器IDR,16位,一個引腳對應1位。
BS0為1,間接ODR0為1;BR0為1,間接導致OER0為0.如果同時啟動BSy和BRy,只有BS起作用。
? ? ? ? LCK0直接對ODR0進行鎖定。鎖定之后在下次端口位復位前不改配置。
5.Keil+VSCode優化開發體驗
為了讓頁面更好看,聯想能力更強。就用keil負責與32的聯接燒錄,VSCode復制寫代碼。
VSCode我之前在C語言的教程中,安裝過,這里不再闡述,關于漢化和C/C++的擴展插件也下載好。
關鍵插件:Keil Assistant
?
5.1 下載安裝VScode
6.GPIO應用案例:流水燈
需求:在三個LED燈上實現流水燈效果。
注意點:加入延時效果(定時器)。
6.1軟件設計
之前我們有過點燈的案例,啟動文件和用戶文件已經配好,那么可以直接復制使用。將文件的名字和工程名字改過來就可以了。刪掉無關文件和目錄(編譯器自動生成)。
點開工程,完成一系列必要的配置。連上硬件和S-TINK.
之后確定即可。
在VScode里面打開.
導入文件,后寫代碼
配置GPIOA時鐘,再把三個GPIO引腳配成通用推挽輸出。記住位或置一,位于置零。
然后我們想想,初始狀態得讓他們全關燈,然后一個一個亮,前一個亮,后一個就得滅。
我們需要定義延時函數。
我們這個芯片可以用系統滴答定時器。每滴答一次-1.那么多長時間滴答一次?
CPU主頻72MHZ,所以一秒鐘72M次滴答,1/72M?秒滴答一次。
這樣就可以實現流水燈了。
#include "stm32f10x.h"
//定義延時函數
void delay_ms(u16 ms);
void delay_us(u16 us);
void delay_s(u16 s);int main(void)
{//1.時鐘配置,開啟GPIOA時鐘RCC->APB2ENR|=RCC_APB2ENR_IOPAEN;//2.工作模式配置,PA0 PA1 PA8通用推挽輸出 CNF=00,MODE=11GPIOA->CRL&=~GPIO_CRL_CNF0;GPIOA->CRL|=GPIO_CRL_MODE0;GPIOA->CRL&=~GPIO_CRL_CNF1;GPIOA->CRL|=GPIO_CRL_MODE1;GPIOA->CRH&=~GPIO_CRH_CNF8;GPIOA->CRH|=GPIO_CRH_MODE8;//3.初始全高電平,都置1GPIOA->ODR|=GPIO_ODR_ODR0;GPIOA->ODR|=GPIO_ODR_ODR1;GPIOA->ODR|=GPIO_ODR_ODR8;//4.在循環中依次點亮,延遲一段時間關閉while(1){//點亮LED1GPIOA->ODR&=~GPIO_ODR_ODR0;//延時半秒delay_ms(500);//關閉LED1GPIOA->ODR|=GPIO_ODR_ODR0;//點亮LED2GPIOA->ODR&=~GPIO_ODR_ODR1;//延時半秒delay_ms(500);//關閉LED2GPIOA->ODR|=GPIO_ODR_ODR1;//點亮LED3GPIOA->ODR&=~GPIO_ODR_ODR8;//延時半秒delay_ms(500);//關閉LED3GPIOA->ODR|=GPIO_ODR_ODR8;}}void delay_us(u16 us)
{//設置系統定時器的初始計數值SysTick->LOAD=72 * us;//配置系統定時器SysTick->CTRL=0x05;//輪詢等待計數值變為0,countflag-1while(!(SysTick->CTRL&SysTick_CTRL_COUNTFLAG)){}
//關閉定時器
SysTick->CTRL&=~SysTick_CTRL_ENABLE;
}void delay_s(u16 s)
{while (s--){delay_ms(1000);}
}void delay_ms(u16 ms)
{while (ms--){delay_us(1000);}}