一:EXTI外部中斷(external interrupt)
1.1 STM32 中斷系統(tǒng)
中斷是指在主程序運(yùn)行過(guò)程中,出現(xiàn)了特定的中斷觸發(fā)條件(中斷源),使得CPU暫停當(dāng)前的程序,轉(zhuǎn)而去處理中斷程序,處理完成后又返回原來(lái)被暫停的位置繼續(xù)執(zhí)行,當(dāng)中斷發(fā)生時(shí)是由硬件自動(dòng)調(diào)用中斷函數(shù)執(zhí)行的,期間編譯器會(huì)保護(hù)現(xiàn)場(chǎng)最后還原現(xiàn)場(chǎng)。使得中斷系統(tǒng)極大程度地提高程序的效率,就像是給自己定鬧鐘,可以不用擔(dān)心錯(cuò)過(guò)時(shí)間而可以安心睡覺(jué),在這個(gè)過(guò)程中,有如下概念:
- 中斷優(yōu)先級(jí):當(dāng)有多個(gè)中斷源同時(shí)申請(qǐng)中斷時(shí),CPU會(huì)根據(jù)中斷源的輕重緩急進(jìn)行裁決,優(yōu)先響應(yīng)更加緊急的中斷源。
- 中斷嵌套:當(dāng)一個(gè)中斷程序正在運(yùn)行時(shí),又有新的更高優(yōu)先級(jí)的中斷源申請(qǐng)中斷,CPU再次暫停當(dāng)前中斷程序,CPU再次暫停當(dāng)前中斷程序,轉(zhuǎn)而去處理新的中斷程序,處理完成后依次進(jìn)行返回。
stm32的F1系列總共有68個(gè)可屏蔽中斷通道(中斷源),包含EXTI、TIM、ADC、USART、SPI、I2C、RTC等多個(gè)外設(shè)。所有的中斷使用嵌套向量中斷控制器NVIC統(tǒng)一管理中斷,每個(gè)中斷通道都擁有16個(gè)可編程的優(yōu)先等級(jí),可對(duì)優(yōu)先級(jí)進(jìn)行分組,進(jìn)一步設(shè)置搶占優(yōu)先級(jí)和響應(yīng)優(yōu)先級(jí)。具體到某一個(gè)型號(hào)的芯片可能不會(huì)有這多中斷,具體需要查看的芯片手冊(cè)。下面是手冊(cè)中的中斷向量表節(jié)選:
- 地址(最后一列):存儲(chǔ)中斷地址,這個(gè)地址列表也稱為中斷向量表。因?yàn)槌绦蛑械闹袛嗪瘮?shù)地址由編譯器來(lái)分配,所以中斷函數(shù)地址不固定。但由于硬件的限制,中斷跳轉(zhuǎn)只能跳轉(zhuǎn)到固定的地址執(zhí)行程序。所以為了讓硬件能跳轉(zhuǎn)到(一個(gè)地址不固定的)中斷函數(shù)里,就需要在內(nèi)存中定義一個(gè)固定的地址列表。當(dāng)中斷發(fā)生后,首先跳轉(zhuǎn)到這個(gè)固定的地址列表,編譯器會(huì)在這個(gè)固定的位置加上一條跳轉(zhuǎn)到中斷函數(shù)的代碼,于是中斷跳轉(zhuǎn)就可以跳轉(zhuǎn)到任意位置了。中斷地址列表就是中斷向量表,相當(dāng)于是中斷跳轉(zhuǎn)的跳板。C語(yǔ)言編程無(wú)需關(guān)注中斷向量表,匯編語(yǔ)言需要。
上圖給出了嵌套向量中斷控制器NVIC的基本結(jié)構(gòu)示意圖。在stm32中,NVIC用于統(tǒng)一管理中斷和分配中斷優(yōu)先級(jí),屬于內(nèi)核外設(shè),是CPU的小助手,可以讓CPU專注于運(yùn)算。從上圖可以看出:
- NVIC有很多輸入口,每個(gè)都代表一個(gè)中斷線路,如EXTI、TIM、ADC等。
- 每個(gè)中斷線路上的斜杠n表示n條線,因?yàn)橐粋€(gè)外設(shè)可能會(huì)同時(shí)占用多個(gè)中斷通道。
- NVIC只有一個(gè)輸出口,通過(guò)中斷優(yōu)先級(jí)確定中斷執(zhí)行的順序。
NVIC的中斷優(yōu)先級(jí)由優(yōu)先級(jí)寄存器的4位二進(jìn)制(十進(jìn)制0~15)決定,這4位可以進(jìn)行切分,分為高n位的搶占優(yōu)先級(jí)和低(4-n)位的響應(yīng)優(yōu)先級(jí)。
搶占優(yōu)先級(jí)高的可以中斷嵌套,響應(yīng)優(yōu)先級(jí)高的可以優(yōu)先排隊(duì),搶占優(yōu)先級(jí)和響應(yīng)優(yōu)先級(jí)均相同的按中斷號(hào)排隊(duì)。這個(gè)中斷號(hào)就是指中斷向量表的第二列“優(yōu)先級(jí)”。
用醫(yī)院的叫號(hào)系統(tǒng)來(lái)舉例子。假設(shè)醫(yī)生正在給某個(gè)病人看病,外面還有很多病人排隊(duì):
- 新來(lái)的病人 搶占優(yōu)先級(jí)高就相當(dāng)于直接進(jìn)屋打斷醫(yī)生,給自己看病。
- 新來(lái)的病人 響應(yīng)優(yōu)先級(jí)高就相當(dāng)于不打擾醫(yī)生,但直接插隊(duì),排在隊(duì)伍的第一個(gè)。
下表是NVIC優(yōu)先級(jí)的分組方式
分組方式 | 搶占優(yōu)先級(jí) | 響應(yīng)優(yōu)先級(jí) |
---|---|---|
分組0(n = 0) | 0位,取值為0 | 4位,取值為0~15 |
分組1(n = 1) | 1位,取值為0~1 | 3位,取值為0~7 |
分組2(n = 2) | 2位,取值為0~3 | 2位,取值為0~3 |
分組3(n = 3) | 3位,取值0~ 7 | 1位,取值為0~ 1 |
分組4(n = 4) | 4位,取值為0~15 | 0位,取值為0 |
注:NVIC是內(nèi)核外設(shè),更多關(guān)于NVIC的介紹參考“STM32F10xxx Cortex-M3編程手冊(cè)”。 NVIC
- 中斷分組的配置寄存器,在SCB_AIRCR中,PRIGROUP這三位就是用于配置中斷分組的。
1.2 STM32外部中斷EXTI
下圖是外部中斷向量表片選
中斷系統(tǒng)是管理和執(zhí)行中斷的邏輯結(jié)構(gòu),外部中斷是眾多能產(chǎn)生中斷的外設(shè)之一,而EXTI就是其中之一(比如USART、I2C,參考上面的NVIC基本結(jié)構(gòu)),上圖給出了外部中斷向量表。EXTI(external interrupt)外部中斷可以監(jiān)測(cè)指定的GPIO口的電平信號(hào),當(dāng)其指定的GPIO口產(chǎn)生電平變化時(shí),EXTI將立即向NVIC發(fā)出中斷申請(qǐng),經(jīng)過(guò)NVIC裁決后即可中斷CPU主程序,使得CPU執(zhí)行EXTI對(duì)應(yīng)的中斷程序。
- 支持的觸發(fā)方式:上升沿/下降沿/雙邊沿/軟件觸發(fā)(上升沿就是低電平到高電平觸發(fā))
- 支持的GPIO口:所有GPIO口,但相同的Pin不能同時(shí)觸發(fā)中斷(比如gpioa_Pin1與gpiob_pin1不能同時(shí)觸發(fā)中斷)。
- 通道數(shù):16個(gè)GPIO_Pin,外加PVD輸出、RTC鬧鐘、USB喚醒、以太網(wǎng)喚醒,共20個(gè)。
注:后面這四個(gè)功能是為了實(shí)現(xiàn)一些特殊的功能,比如想實(shí)現(xiàn)某個(gè)時(shí)間讓stm32退出停止模式,由于外部中斷可以在低功耗模式的停止模式下喚醒stm32,就可以在GPIO口上連接一個(gè)RTC時(shí)鐘作為外部中斷。
- 觸發(fā)響應(yīng)方式:中斷響應(yīng)/事件響應(yīng)。
- 注意:
中斷響應(yīng)就是正常的中斷流程,申請(qǐng)中斷讓CPU執(zhí)行中斷函數(shù);- 事件響應(yīng)就是外部中斷發(fā)生時(shí),不把外部信號(hào)給CPU,而是選擇觸發(fā)一個(gè)事件,將這個(gè)信號(hào)通向其他外設(shè),來(lái)觸發(fā)其他外設(shè)的操作,可以實(shí)現(xiàn)外設(shè)之間的聯(lián)合工作(中介分包)。
下圖是EXTI的基本結(jié)構(gòu)Alternate Fuction I/O:數(shù)據(jù)選擇器(交替作用)
記形狀:梯形(多個(gè)輸入一個(gè)輸出)
- 最左側(cè):GPIO口的外設(shè),每個(gè)外設(shè)都有16個(gè)引腳。
- AFIO中斷引腳選擇:本質(zhì)上就是數(shù)據(jù)選擇器,從前面16*n 個(gè)引腳中選擇16根端口號(hào)不重復(fù)的引腳出來(lái),連接到后面的EXTI通道中。在STM32中,AFIO主要完成兩個(gè)任務(wù):復(fù)用功能引腳重映射、中斷引腳選擇。下面是中斷引腳選擇的AFIO示意圖:
![]()
- PVD、RTC、USB、ETH:四個(gè)特殊功能的外設(shè)。
- EXTI 邊沿檢測(cè)及控制:20個(gè)輸入通道、兩類輸出。一類輸出到NVIC中,并且將這20路輸出的9、10 路外部中斷合并在一起以節(jié)省通道;另一類輸出到其他外設(shè),直接就是20路輸出。
注:上面這個(gè)EXTI的基本結(jié)構(gòu)也是編寫代碼時(shí)的主要參考圖!
下圖是EXTI 框圖(方向?yàn)閺淖蟮接遥?stm32F10系列參考手冊(cè)
上圖給出了參考手冊(cè)中的EXTI框圖?;具壿嬇c“EXTI的基本結(jié)構(gòu)”中所述相同,另外還有一些細(xì)節(jié):
- 邊沿檢測(cè)電路+軟件中斷事件寄存器:這個(gè)幾個(gè)進(jìn)行或門輸出,便可以實(shí)現(xiàn)“上升沿/下降沿/雙邊沿/軟件觸發(fā)”這四種觸發(fā)方式。
- 請(qǐng)求掛起寄存器:相當(dāng)于一個(gè)中斷標(biāo)志位,通過(guò)讀取該寄存器可以判斷是哪個(gè)通道觸發(fā)的中斷。
- 中斷屏蔽寄存器/事件屏蔽寄存器:相當(dāng)于開(kāi)關(guān),只有置1,中斷信號(hào)才能繼續(xù)向左走。
- 脈沖發(fā)生器:產(chǎn)生一個(gè)電平脈沖,用于觸發(fā)其他外設(shè)工作。
最后一個(gè)問(wèn)題,到底什么樣的設(shè)備需要用到外部中段呢?
答:對(duì)于stm32來(lái)說(shuō),若想獲取一個(gè)由外部驅(qū)動(dòng)的很快的突發(fā)信號(hào),就需要外部中斷。
如旋轉(zhuǎn)編碼器,平常不會(huì)有什么變化,但是一旦擰動(dòng)時(shí),會(huì)產(chǎn)生一段時(shí)間變化很快的突發(fā)信號(hào),就需要stm32能在短時(shí)間內(nèi)快速讀取并處理掉這個(gè)數(shù)據(jù)。
再如紅外遙控接收頭,平常也不會(huì)有什么變化,但是一旦接收到信號(hào)時(shí),這個(gè)信號(hào)也是轉(zhuǎn)瞬即逝的。
但是不推薦按鍵使用外部中斷。因?yàn)橥獠恐袛嗖荒芎芎玫奶幚戆存I抖動(dòng)和松手檢測(cè)的問(wèn)題,所以要求不高時(shí),還是建議在主函數(shù)內(nèi)部循環(huán)讀取。
1.2 旋轉(zhuǎn)編碼器介紹
對(duì)射式紅外傳感器就是一種通用傳感器模塊,已經(jīng)在第三節(jié)“GPIO通用輸入輸出口”中介紹過(guò),不再贅述。本實(shí)驗(yàn)只介紹旋轉(zhuǎn)編碼器。
旋轉(zhuǎn)編碼器是一種用來(lái)測(cè)量位置、速度或旋轉(zhuǎn)方向的裝置,當(dāng)其旋轉(zhuǎn)軸旋轉(zhuǎn)時(shí),其輸出端可以與旋轉(zhuǎn)速度和方向?qū)?yīng)的方波信號(hào),讀取方波信號(hào)的頻率和相位信息即可得知旋轉(zhuǎn)軸的速度和方向。
如上圖,旋轉(zhuǎn)編碼器主要有三種類型:光柵式/機(jī)械觸點(diǎn)式/霍爾傳感器式。下面是這三種形式的介紹:
- 光柵式(老款鼠標(biāo)):配合對(duì)射式紅外傳感器使用,在旋轉(zhuǎn)過(guò)程中光柵式編碼盤會(huì)不斷地阻擋/透過(guò)紅外射線,于是模塊便會(huì)輸出高低電平交替的方波,方波的頻率便代表了旋轉(zhuǎn)速度。缺點(diǎn)是只有一路輸出,無(wú)法判斷轉(zhuǎn)動(dòng)方向。
- 機(jī)械觸點(diǎn)式:內(nèi)部使用機(jī)械觸點(diǎn)檢測(cè)通斷,A口和B口輸出的方波正交,具體看下面的介紹。當(dāng)然,也有機(jī)械觸點(diǎn)式編碼器可以一個(gè)引腳輸出速度信息,一一個(gè)引腳輸出旋轉(zhuǎn)方向信息。
- 霍爾傳感器式:直接附在電機(jī)后面的編碼器,中間是一個(gè)圓形磁鐵,旋轉(zhuǎn)時(shí)兩側(cè)的霍爾傳感器便可輸出正交的方波信號(hào)。
- 獨(dú)立的編碼器元件:輸入軸轉(zhuǎn)動(dòng)時(shí),輸出便有波形。
注:觸點(diǎn)式不適合高速旋轉(zhuǎn)的場(chǎng)景,常用于音量調(diào)節(jié)。非接觸式形式的電機(jī)可以用于電機(jī)測(cè)速。
上圖是機(jī)械觸點(diǎn)式旋轉(zhuǎn)編碼器-實(shí)物拆解
- 圖片右側(cè)是旋轉(zhuǎn)編碼器的旋鈕,可以看到下面是一圈可以導(dǎo)電的金屬片。
- 中間有一個(gè)大的按鍵開(kāi)關(guān)結(jié)構(gòu),也可以檢測(cè)通斷,但是該旋轉(zhuǎn)編碼器模塊沒(méi)有使用到該功能。
- 左右兩組金屬觸點(diǎn)。內(nèi)部實(shí)際的連接如紅線標(biāo)注,C口接地,于是旋鈕在旋轉(zhuǎn)過(guò)程中就可以使A口、B口輸出高低交替的方波。方波頻率表示旋轉(zhuǎn)速度。
- A口、B口配合旋鈕,可以產(chǎn)生相位相差90°的方波,稱為正交信號(hào)。順時(shí)針旋轉(zhuǎn)A口相位超前,逆時(shí)針旋轉(zhuǎn)B口相位超前。
- R1、R2:上拉電阻。
- R3、R4:輸出限流電阻,防止引腳電流過(guò)大。
- C1、C2:濾波電容,濾除高頻不穩(wěn)定紋波。
注:C口已經(jīng)默認(rèn)接地,只需關(guān)心A口、B口的高低變化及相位差即可。
1.3 實(shí)驗(yàn):對(duì)射式紅外傳感器計(jì)次
需求:利用stm32的外部中斷,對(duì) 對(duì)射式紅外傳感器 產(chǎn)生的下降沿進(jìn)行計(jì)次。
上圖是對(duì)射式紅外傳感器計(jì)次-接線圖
下圖是對(duì)射式紅外傳感器計(jì)次-代碼調(diào)用代碼展示:OLED.h、OLED.c、OLED_Font.h代碼見(jiàn)第四節(jié)“OLED調(diào)試工具”,本節(jié)省略。
文章在這《嵌入式-stm32-江科大-OLED調(diào)試工具》
main.c
#include "stm32f10x.h"
#include "Delay.h"
#include "OLED.h"
#include "CountSensor.h"
int main(void)
{
//模塊初始化
OLED_Init(); //OLED初始化
CountSensor_Init(); //計(jì)數(shù)傳感器初始化
//OLED顯示,顯示靜態(tài)字符串
OLED_ShowString(1,1,"Count:"); //一行一列顯示字符串
Delay_ms(500);
while(1)
{
OLED_ShowNum(2,1,CountSensor_Get(),5);
}
}
CountSensor.h
#ifndef __COUNT_SENSOR_H
#define __COUNT_SENSOR_H
void CountSensor_Init(void);
uint16_t CountSensor_Get(void);
#endif
CountSensor.c
#include "stm32f10x.h"
uint16_t CountSensor_Count; //全局變量,中斷觸發(fā)次數(shù),用于計(jì)數(shù)
//默認(rèn)初始化為0
/**
* 函 數(shù):對(duì)射式紅外傳感器-計(jì)數(shù)傳感器初始化 PB14
* 參 數(shù):無(wú)
* 返 回 值:無(wú)
*/
void CountSensor_Init(void)
{
//1.開(kāi)啟時(shí)鐘
//2.GPIO初始化
//3.AFIO選擇中斷引腳
//4.EXTI初始化
//5.//NVIC配置,NVIC中斷分組
//EXTI初始化
//1.開(kāi)啟GPIO&AFIO的外設(shè)時(shí)鐘(EXTI和NVIC的時(shí)鐘是一直打開(kāi)的)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
//2.配置GPIO—PB14上拉輸入
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode =GPIO_Mode_IPU; //上拉輸入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);
//3.配置AFIO(庫(kù)函數(shù)在GPIO中)中斷引腳選擇
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource14);//將外部中斷的14號(hào)線映射到GPIOB,即選擇PB14為外部中斷引腳
//4.配置EXTI初始化
EXTI_InitTypeDef EXTI_InitStructure;//改名字
EXTI_InitStructure.EXTI_Line = EXTI_Line14;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;//下降沿觸發(fā)
EXTI_Init(&EXTI_InitStructure);
//5.NVIC配置(庫(kù)函數(shù)在misc.h文件中,雜項(xiàng))
//配置NVIC中斷分組
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
//配置中斷的優(yōu)先級(jí)分組,每個(gè)工程只能出現(xiàn)一次
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
}
/**
* 函 數(shù):獲取計(jì)數(shù)傳感器的計(jì)數(shù)值,輸出中斷觸發(fā)的次數(shù)
* 參 數(shù):無(wú)
* 返 回 值:計(jì)數(shù)值,范圍:0~65535
*/
uint16_t CountSensor_Get(void)
{
return CountSensor_Count;
}
/**
* 函 數(shù):EXTI15_10外部中斷函數(shù)
* 參 數(shù):無(wú)
* 返 回 值:無(wú)
* 注意事項(xiàng):此函數(shù)為中斷函數(shù),無(wú)需調(diào)用,中斷觸發(fā)后自動(dòng)執(zhí)行
* 函數(shù)名為預(yù)留的指定名稱,可以從啟動(dòng)文件復(fù)制
* 請(qǐng)確保函數(shù)名正確,不能有任何差異,否則中斷函數(shù)將不能進(jìn)入
*/
void EXTI15_10_IRQHandler(void)
{
//中斷標(biāo)志位判斷
if(EXTI_GetITStatus(EXTI_Line14) == SET) //判斷是否是外部中斷14號(hào)線觸發(fā)的中斷
{
//如果出現(xiàn)數(shù)據(jù)亂跳的現(xiàn)象,可再次判斷引腳電平,以避免抖動(dòng)
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_14) == 0 )
{
CountSensor_Count ++; //計(jì)數(shù)值自增一次
}
EXTI_ClearITPendingBit(EXTI_Line4);//清除外部中斷14號(hào)線的中斷標(biāo)志位
//中斷標(biāo)志位必須清除
//否則中斷將連續(xù)不斷地觸發(fā),導(dǎo)致主程序卡死
}
}
1.31 編程感想
對(duì)射式傳感器采用下降沿觸發(fā)(移除遮擋觸發(fā),硬件設(shè)計(jì))。
傳感器無(wú)遮擋時(shí),DO輸出低電平;傳感器有遮擋時(shí),DO輸出高電平。所以放入遮擋物意味著觸發(fā)上升沿,移除遮擋相當(dāng)于下降沿。采用上升沿觸發(fā)計(jì)數(shù)可能不準(zhǔn)確,下降沿觸發(fā)計(jì)數(shù)準(zhǔn)確(實(shí)測(cè)),若傳感器采用上升沿觸發(fā)那么結(jié)論和上面相反,移除遮擋物的時(shí)候比較準(zhǔn)確。中斷函數(shù)的名字從啟動(dòng)文件“stratup_stm32f10x_md”中來(lái),并且中斷函數(shù)都是無(wú)參無(wú)返回值的。
在CountSensor.c里面GPIO使用結(jié)構(gòu)體來(lái)初始化函數(shù)、外部中斷、ADC、串口都是一樣的操作。
學(xué)會(huì)使用keil調(diào)試,博主第一次燒錄程序發(fā)現(xiàn)計(jì)數(shù)傳感器沒(méi)有返回值,燈會(huì)閃計(jì)數(shù),但是不會(huì)反饋到顯示屏上。于是我先把老師的代碼燒錄運(yùn)行成功,說(shuō)明硬件沒(méi)問(wèn)題,就是自己代碼問(wèn)題,然后一直排除,通過(guò)用源碼替換自己寫的代碼過(guò)程中,發(fā)現(xiàn)是上一個(gè)實(shí)驗(yàn)的OLED.c問(wèn)題,CV工程師。
OLED.c(可以跳過(guò))
#include "stm32f10x.h"
#include "OLED_Font.h"
/*引腳配置*/
#define OLED_W_SCL(x) GPIO_WriteBit(GPIOB, GPIO_Pin_8, (BitAction)(x))
#define OLED_W_SDA(x) GPIO_WriteBit(GPIOB, GPIO_Pin_9, (BitAction)(x))
/*引腳初始化*/
void OLED_I2C_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_Init(GPIOB, &GPIO_InitStructure);
OLED_W_SCL(1);
OLED_W_SDA(1);
}
/**
* @brief I2C開(kāi)始
* @param 無(wú)
* @retval 無(wú)
*/
void OLED_I2C_Start(void)
{
OLED_W_SDA(1);
OLED_W_SCL(1);
OLED_W_SDA(0);
OLED_W_SCL(0);
}
/**
* @brief I2C停止
* @param 無(wú)
* @retval 無(wú)
*/
void OLED_I2C_Stop(void)
{
OLED_W_SDA(0);
OLED_W_SCL(1);
OLED_W_SDA(1);
}
/**
* @brief I2C發(fā)送一個(gè)字節(jié)
* @param Byte 要發(fā)送的一個(gè)字節(jié)
* @retval 無(wú)
*/
void OLED_I2C_SendByte(uint8_t Byte)
{
uint8_t i;
for (i = 0; i < 8; i++)
{
OLED_W_SDA(Byte & (0x80 >> i));
OLED_W_SCL(1);
OLED_W_SCL(0);
}
OLED_W_SCL(1); //額外的一個(gè)時(shí)鐘,不處理應(yīng)答信號(hào)
OLED_W_SCL(0);
}
/**
* @brief OLED寫命令
* @param Command 要寫入的命令
* @retval 無(wú)
*/
void OLED_WriteCommand(uint8_t Command)
{
OLED_I2C_Start();
OLED_I2C_SendByte(0x78); //從機(jī)地址
OLED_I2C_SendByte(0x00); //寫命令
OLED_I2C_SendByte(Command);
OLED_I2C_Stop();
}
/**
* @brief OLED寫數(shù)據(jù)
* @param Data 要寫入的數(shù)據(jù)
* @retval 無(wú)
*/
void OLED_WriteData(uint8_t Data)
{
OLED_I2C_Start();
OLED_I2C_SendByte(0x78); //從機(jī)地址
OLED_I2C_SendByte(0x40); //寫數(shù)據(jù)
OLED_I2C_SendByte(Data);
OLED_I2C_Stop();
}
/**
* @brief OLED設(shè)置光標(biāo)位置
* @param Y 以左上角為原點(diǎn),向下方向的坐標(biāo),范圍:0~7
* @param X 以左上角為原點(diǎn),向右方向的坐標(biāo),范圍:0~127
* @retval 無(wú)
*/
void OLED_SetCursor(uint8_t Y, uint8_t X)
{
OLED_WriteCommand(0xB0 | Y); //設(shè)置Y位置
OLED_WriteCommand(0x10 | ((X & 0xF0) >> 4)); //設(shè)置X位置高4位
OLED_WriteCommand(0x00 | (X & 0x0F)); //設(shè)置X位置低4位
}
/**
* @brief OLED清屏
* @param 無(wú)
* @retval 無(wú)
*/
void OLED_Clear(void)
{
uint8_t i, j;
for (j = 0; j < 8; j++)
{
OLED_SetCursor(j, 0);
for(i = 0; i < 128; i++)
{
OLED_WriteData(0x00);
}
}
}
/**
* @brief OLED顯示一個(gè)字符
* @param Line 行位置,范圍:1~4
* @param Column 列位置,范圍:1~16
* @param Char 要顯示的一個(gè)字符,范圍:ASCII可見(jiàn)字符
* @retval 無(wú)
*/
void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char)
{
uint8_t i;
OLED_SetCursor((Line - 1) * 2, (Column - 1) * 8); //設(shè)置光標(biāo)位置在上半部分
for (i = 0; i < 8; i++)
{
OLED_WriteData(OLED_F8x16[Char - ' '][i]); //顯示上半部分內(nèi)容
}
OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 8); //設(shè)置光標(biāo)位置在下半部分
for (i = 0; i < 8; i++)
{
OLED_WriteData(OLED_F8x16[Char - ' '][i + 8]); //顯示下半部分內(nèi)容
}
}
/**
* @brief OLED顯示字符串
* @param Line 起始行位置,范圍:1~4
* @param Column 起始列位置,范圍:1~16
* @param String 要顯示的字符串,范圍:ASCII可見(jiàn)字符
* @retval 無(wú)
*/
void OLED_ShowString(uint8_t Line, uint8_t Column, char *String)
{
uint8_t i;
for (i = 0; String[i] != '\0'; i++)
{
OLED_ShowChar(Line, Column + i, String[i]);
}
}
/**
* @brief OLED次方函數(shù)
* @retval 返回值等于X的Y次方
*/
uint32_t OLED_Pow(uint32_t X, uint32_t Y)
{
uint32_t Result = 1;
while (Y--)
{
Result *= X;
}
return Result;
}
/**
* @brief OLED顯示數(shù)字(十進(jìn)制,正數(shù))
* @param Line 起始行位置,范圍:1~4
* @param Column 起始列位置,范圍:1~16
* @param Number 要顯示的數(shù)字,范圍:0~4294967295
* @param Length 要顯示數(shù)字的長(zhǎng)度,范圍:1~10
* @retval 無(wú)
*/
void OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
uint8_t i;
for (i = 0; i < Length; i++)
{
OLED_ShowChar(Line, Column + i, Number / OLED_Pow(10, Length - i - 1) % 10 + '0');
}
}
/**
* @brief OLED顯示數(shù)字(十進(jìn)制,帶符號(hào)數(shù))
* @param Line 起始行位置,范圍:1~4
* @param Column 起始列位置,范圍:1~16
* @param Number 要顯示的數(shù)字,范圍:-2147483648~2147483647
* @param Length 要顯示數(shù)字的長(zhǎng)度,范圍:1~10
* @retval 無(wú)
*/
void OLED_ShowSignedNum(uint8_t Line, uint8_t Column, int32_t Number, uint8_t Length)
{
uint8_t i;
uint32_t Number1;
if (Number >= 0)
{
OLED_ShowChar(Line, Column, '+');
Number1 = Number;
}
else
{
OLED_ShowChar(Line, Column, '-');
Number1 = -Number;
}
for (i = 0; i < Length; i++)
{
OLED_ShowChar(Line, Column + i + 1, Number1 / OLED_Pow(10, Length - i - 1) % 10 + '0');
}
}
/**
* @brief OLED顯示數(shù)字(十六進(jìn)制,正數(shù))
* @param Line 起始行位置,范圍:1~4
* @param Column 起始列位置,范圍:1~16
* @param Number 要顯示的數(shù)字,范圍:0~0xFFFFFFFF
* @param Length 要顯示數(shù)字的長(zhǎng)度,范圍:1~8
* @retval 無(wú)
*/
void OLED_ShowHexNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
uint8_t i, SingleNumber;
for (i = 0; i < Length; i++)
{
SingleNumber = Number / OLED_Pow(16, Length - i - 1) % 16;
if (SingleNumber < 10)
{
OLED_ShowChar(Line, Column + i, SingleNumber + '0');
}
else
{
OLED_ShowChar(Line, Column + i, SingleNumber - 10 + 'A');
}
}
}
/**
* @brief OLED顯示數(shù)字(二進(jìn)制,正數(shù))
* @param Line 起始行位置,范圍:1~4
* @param Column 起始列位置,范圍:1~16
* @param Number 要顯示的數(shù)字,范圍:0~1111 1111 1111 1111
* @param Length 要顯示數(shù)字的長(zhǎng)度,范圍:1~16
* @retval 無(wú)
*/
void OLED_ShowBinNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
uint8_t i;
for (i = 0; i < Length; i++)
{
OLED_ShowChar(Line, Column + i, Number / OLED_Pow(2, Length - i - 1) % 2 + '0');
}
}
/**
* @brief OLED初始化
* @param 無(wú)
* @retval 無(wú)
*/
void OLED_Init(void)
{
uint32_t i, j;
for (i = 0; i < 1000; i++) //上電延時(shí)
{
for (j = 0; j < 1000; j++);
}
OLED_I2C_Init(); //端口初始化
OLED_WriteCommand(0xAE); //關(guān)閉顯示
OLED_WriteCommand(0xD5); //設(shè)置顯示時(shí)鐘分頻比/振蕩器頻率
OLED_WriteCommand(0x80);
OLED_WriteCommand(0xA8); //設(shè)置多路復(fù)用率
OLED_WriteCommand(0x3F);
OLED_WriteCommand(0xD3); //設(shè)置顯示偏移
OLED_WriteCommand(0x00);
OLED_WriteCommand(0x40); //設(shè)置顯示開(kāi)始行
OLED_WriteCommand(0xA1); //設(shè)置左右方向,0xA1正常 0xA0左右反置
OLED_WriteCommand(0xC8); //設(shè)置上下方向,0xC8正常 0xC0上下反置
OLED_WriteCommand(0xDA); //設(shè)置COM引腳硬件配置
OLED_WriteCommand(0x12);
OLED_WriteCommand(0x81); //設(shè)置對(duì)比度控制
OLED_WriteCommand(0xCF);
OLED_WriteCommand(0xD9); //設(shè)置預(yù)充電周期
OLED_WriteCommand(0xF1);
OLED_WriteCommand(0xDB); //設(shè)置VCOMH取消選擇級(jí)別
OLED_WriteCommand(0x30);
OLED_WriteCommand(0xA4); //設(shè)置整個(gè)顯示打開(kāi)/關(guān)閉
OLED_WriteCommand(0xA6); //設(shè)置正常/倒轉(zhuǎn)顯示
OLED_WriteCommand(0x8D); //設(shè)置充電泵
OLED_WriteCommand(0x14);
OLED_WriteCommand(0xAF); //開(kāi)啟顯示
OLED_Clear(); //OLED清屏
}
1.4 實(shí)驗(yàn):旋轉(zhuǎn)編碼器計(jì)次
需求:利用stm32的外部中斷,對(duì)旋轉(zhuǎn)編碼器的轉(zhuǎn)動(dòng)進(jìn)行計(jì)次,順時(shí)針加、逆時(shí)針減,并顯示在OLED顯示屏上。上圖是旋轉(zhuǎn)編碼器計(jì)次的接線圖,下圖是代碼調(diào)用(除庫(kù)函數(shù)以外)
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "CountSensor.h"
#include "Encoder.h"
int16_t Num; //定義待被旋轉(zhuǎn)編碼器調(diào)節(jié)的變量
int main(void)
{
/*模塊初始化*/
OLED_Init(); //OLED初始化
CountSensor_Init(); //計(jì)數(shù)傳感器初始化
Encoder_Init(); //旋轉(zhuǎn)編碼器初始化
/*顯示靜態(tài)字符串*/
OLED_ShowString(1, 1, "Num:"); //1行1列顯示字符串Count:
OLED_ShowString(2, 1, "Num+=:"); //1行1列顯示字符串Count:
while (1)
{
Num += Encoder_Get();
OLED_ShowSignedNum(1, 7, CountSensor_Get(), 5); //OLED不斷刷新顯示CountSensor_Get的返回值
OLED_ShowSignedNum(2,8,Num,5); //顯示Num
}
}
Encoder.h
#ifndef __ENCODER_H
#define __ENCODER_H
void Encoder_Init(void);
int16_t Encoder_Get(void);
#endif
Encoder.c文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-824774.html
#include "stm32f10x.h"
int16_t Encoder_Count; //全局變量,用于計(jì)數(shù)旋轉(zhuǎn)編碼器的增量值
void Encoder_Init(void)//旋轉(zhuǎn)編碼器初始化
{
/*開(kāi)啟時(shí)鐘*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //開(kāi)啟GPIOB的時(shí)鐘
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //開(kāi)啟AFIO的時(shí)鐘,外部中斷必須開(kāi)啟AFIO的時(shí)鐘
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;//AB相分別接的是PB0和PB1
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure); //將PB0和PB1引腳初始化為上拉輸入
/*AFIO選擇中斷引腳*/
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource0);//將外部中斷的0號(hào)線映射到GPIOB,即選擇PB0為外部中斷引腳
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource1);//將外部中斷的1號(hào)線映射到GPIOB,即選擇PB1為外部中斷引腳
/*EXTI初始化*/
EXTI_InitTypeDef EXTI_InitStructure; //定義結(jié)構(gòu)體變量
EXTI_InitStructure.EXTI_Line = EXTI_Line0 | EXTI_Line1; //選擇配置外部中斷的0號(hào)線和1號(hào)線
EXTI_InitStructure.EXTI_LineCmd = ENABLE; //指定外部中斷線使能
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //指定外部中斷線為中斷模式
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //指定外部中斷線為下降沿觸發(fā)
EXTI_Init(&EXTI_InitStructure); //將結(jié)構(gòu)體變量交給EXTI_Init,配置EXTI外設(shè)
/*NVIC中斷分組*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC為分組2
//即搶占優(yōu)先級(jí)范圍:0~3,響應(yīng)優(yōu)先級(jí)范圍:0~3
//此分組配置在整個(gè)工程中僅需調(diào)用一次
//若有多個(gè)中斷,可以把此代碼放在main函數(shù)內(nèi),while循環(huán)之前
//若調(diào)用多次配置分組的代碼,則后執(zhí)行的配置會(huì)覆蓋先執(zhí)行的配置
/*NVIC配置*/
NVIC_InitTypeDef NVIC_InitStructure; //定義結(jié)構(gòu)體變量
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; //選擇配置NVIC的EXTI0線
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC線路使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //指定NVIC線路的搶占優(yōu)先級(jí)為1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC線路的響應(yīng)優(yōu)先級(jí)為1
NVIC_Init(&NVIC_InitStructure); //將結(jié)構(gòu)體變量交給NVIC_Init,配置NVIC外設(shè)
NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn; //選擇配置NVIC的EXTI1線
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC線路使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //指定NVIC線路的搶占優(yōu)先級(jí)為1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //指定NVIC線路的響應(yīng)優(yōu)先級(jí)為2
NVIC_Init(&NVIC_InitStructure);
}
//打算每次調(diào)用get函數(shù)之后,返回count的變化值,用于外部加減一個(gè)變量
int16_t Encoder_Get(void)
{
//使用Temp變量作為中繼,目的是返回Encoder_Count后將其清零,目的是給count清零
//在這里,也可以直接返回Encoder_Count,直接返回就退出函數(shù)了沒(méi)辦法給count清零
//下面這樣子又能獲取到count變化值,又能給count清零
//直接返回count會(huì)導(dǎo)致你扭一下,count就有一個(gè)值,Num就會(huì)一直變化了
int16_t Temp;
Temp = Encoder_Count;
Encoder_Count= 0;
return Temp;
}
void EXTI0_IRQHandler(void)//A口下降沿中斷函數(shù)
{
if(EXTI_GetITStatus(EXTI_Line0) == SET)
{
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1) == 0) //PB1的下降沿觸發(fā)中斷,此時(shí)檢測(cè)另一相,等于1正轉(zhuǎn),等于0反轉(zhuǎn)
{
Encoder_Count --; //此方向定義為反轉(zhuǎn),計(jì)數(shù)變量自減
}
EXTI_ClearITPendingBit(EXTI_Line0); //清除標(biāo)志中斷0號(hào)線的中斷標(biāo)志位
}
}
void EXTI1_IRQHandler(void)//B口下降沿中斷函數(shù)
{
if(EXTI_GetITStatus(EXTI_Line1) == SET)
{
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1) == 0) //PB1的下降沿觸發(fā)中斷,此時(shí)檢測(cè)另一相
{
Encoder_Count ++; //此方向定義為正轉(zhuǎn),計(jì)數(shù)變量自增
}
EXTI_ClearITPendingBit(EXTI_Line1); //清除標(biāo)志中斷1號(hào)線的中斷標(biāo)志位
}
}
1.41 編程感想
- 管理Hardware文件夾。本次實(shí)驗(yàn)繼承的是“OLED顯示屏”實(shí)驗(yàn)的代碼,而非“對(duì)射式紅外傳感器計(jì)次”。
- 注意每個(gè)模塊在使用的時(shí)候都要進(jìn)行初始化。
- 注意進(jìn)入中斷函數(shù)的時(shí)候要檢查中斷標(biāo)志位,,退出的時(shí)候清零中斷標(biāo)志位。
- 注意主函數(shù)和中斷函數(shù)不要操控同一個(gè)硬件,避免不必要的硬件沖突。中斷函數(shù)一般執(zhí)行簡(jiǎn)短快速的代碼,如操作中斷標(biāo)志位等。
- 在中斷函數(shù)內(nèi)部不要執(zhí)行長(zhǎng)時(shí)間函數(shù),中斷是快速的突發(fā)事件,必須要短。
參考:B站STM32江協(xié)自動(dòng)化&【哈工大虎慕】文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-824774.html
道友:沒(méi)有永久的巔峰也沒(méi)有永遠(yuǎn)的低谷,真正的強(qiáng)大不是忘記而是接受,接受世事無(wú)常、接受孤獨(dú)挫敗、接受突如其來(lái)的無(wú)力感、接受自己的不完美、接受困惑不安的焦慮和遺憾。
到了這里,關(guān)于嵌入式-stm32-江科大-EXTI外部中斷的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!