STM32學(xué)習(xí)筆記之SPI_DMA寄存器級(jí)操作
一、實(shí)驗(yàn)?zāi)繕?biāo)
學(xué)會(huì)配置STM32的SPI寄存器和DMA寄存器,實(shí)現(xiàn)STM32的SPI1與SPI2通信功能,每次發(fā)送一字節(jié)數(shù)據(jù),并可多次發(fā)送,如果接收的數(shù)據(jù)正確,則點(diǎn)亮LED燈。
本文引用地址:http://m.butianyuan.cn/article/201611/318033.htm二、實(shí)驗(yàn)?zāi)康?/h2>
加入DMA的SPI通信相對(duì)于普通SPI通信有什么好處?ST給SPI加了DMA功能出于什么目的?我覺得這是很重要的一個(gè)問題,一直邊學(xué)習(xí)邊想。以下是我的看法:
減少CPU負(fù)荷?我想這應(yīng)該是DMA最主要的功能,可是對(duì)于SPI通信來說,其實(shí)大部分時(shí)候我們需要根據(jù)發(fā)送的指令->目標(biāo)器件的應(yīng)答來決定下一個(gè)指令,所以此時(shí)CPU還是需要一直等待每次通信的結(jié)束。而且像SD卡的操作,是一個(gè)順序流的指令操作過程,用中斷也不容易控制。那到底加入了DMA有什么好處?仔細(xì)查看了STM32F10xxx的用戶手冊(cè),發(fā)現(xiàn)這么一行字“連續(xù)和非連續(xù)傳輸:當(dāng)在主模式下發(fā)送數(shù)據(jù)時(shí),如果軟件足夠快,能夠在檢測(cè)到每次TXE的上升沿(或TXE中斷),并立即在正在進(jìn)行的傳輸結(jié)束之前寫入SPI_DR寄存器,則能夠?qū)崿F(xiàn)連續(xù)的通信;此時(shí),在每個(gè)數(shù)據(jù)項(xiàng)的傳輸之間的SPI時(shí)鐘保持連續(xù),同時(shí)BSY位不會(huì)被清除。如果軟件不夠快,則會(huì)導(dǎo)致不連續(xù)的通信;這時(shí),在每個(gè)數(shù)據(jù)傳輸之間會(huì)被清除”以及
也就是說如果連續(xù)傳輸而不使用DMA的話,需要CPU不停檢測(cè)TXE并很快地置入SPI->DR的值,對(duì)于復(fù)雜程序的話這是很難達(dá)到的,而如果使用DMA,就可以輕易實(shí)現(xiàn)連續(xù)傳輸,CPU只需等待其完成就好。我想到的一個(gè)應(yīng)用就是在寫SD卡的時(shí)候,每次寫一個(gè)塊512字節(jié),就可以用到,能提高SD卡的寫入數(shù)據(jù)速率。
其次還可以降低功耗,記得數(shù)字集成電路老師說過一句話“軟件上降低數(shù)字電路功耗的一個(gè)方法就是減少電平轉(zhuǎn)換。”那么連續(xù)通信的時(shí)候,像SPI的BSY電平轉(zhuǎn)換會(huì)大大減少!
最后一點(diǎn),雖然效果不大,就是如果不是用DMA,那么CPU的工作就是搬運(yùn)工,把SPI->DR的內(nèi)容搬到內(nèi)存存儲(chǔ)起來,而如果使用DMA,就省略了這個(gè)環(huán)節(jié)!
我想,為什么實(shí)現(xiàn)同一個(gè)功能,有的執(zhí)行起來很流暢,有的卻很卡,應(yīng)該和這些小細(xì)節(jié)的減載有關(guān)吧。
這次先把SPI基本通信寫出來,然后再寫SPI的連續(xù)通信,并看能不能用到SD卡讀寫上。
三、SPI&DMA分析
1、這里先說明一下SPI的全雙工通信(高手略過哈)
SPI全雙工通信的特點(diǎn):一邊發(fā)送一邊接收,硬件上只有一個(gè)SPI->DR寄存器和兩個(gè)緩沖器(發(fā)送緩沖器和接收緩沖器),主模式(從模式類似):SPI->DR會(huì)先讀發(fā)送緩沖器,并通過MOSI管腳(Master output Slave Input)一位一位地發(fā)送出去,在發(fā)送的過程中,SPI->DR的數(shù)據(jù)會(huì)左移(如果是高位先發(fā)送),并且會(huì)從MISO(Master input Slave output)讀入數(shù)據(jù)填補(bǔ)SPI->DR左移后的空缺。傳完8比特后,SPI->DR再把數(shù)據(jù)并行寫入接收緩沖寄存器。所以,SPI1與SPI2的通信過程如下:
配置SPI寄存器的時(shí)候,需要注意以下幾點(diǎn):
(1)nss的配置:如果是單主單從,使用nss軟件管理,除了用MSTR配置主從設(shè)備,還要設(shè)置SSM和SSI,只有在SSM位為1時(shí),SSI位才有意義。
(2)主從設(shè)備的數(shù)據(jù)幀格式,時(shí)鐘沿讀寫模式要一致;
(3)SPI的寄存器也需要開啟DMA使能;
(4)SPI雖然可以發(fā)送16bit數(shù)據(jù),可是只支持8bitDMA!
2、再說一下DMA
DMA——Direct Memory Access,直接內(nèi)存存取,作用是獨(dú)立于CPU,直接建立內(nèi)存與外設(shè)的通信通道。
SPI的DMA操作,就是在SPI->TXE為1時(shí),會(huì)向?qū)?yīng)的DMA通道發(fā)出請(qǐng)求,DMA通道會(huì)發(fā)出應(yīng)答信號(hào),SPI收到應(yīng)答信號(hào)后撤銷請(qǐng)求信號(hào),DMA撤銷應(yīng)答信號(hào),并把內(nèi)存值置入發(fā)送緩沖,SPI傳送開始。接收過程與上面類似。
DMA配置的部分說明:
(1)需要使能RCC寄存器的SPI和DMA時(shí)鐘,至于輔助時(shí)鐘,查過網(wǎng)上的討論,有人說一些外設(shè)如果沒有開啟輔助時(shí)鐘會(huì)不能用,但SPI不需要;
(2)DMA的存儲(chǔ)器地址(memorybaseaddr):即變量地址。我們?cè)诔绦蛑卸x的每個(gè)變量,都有對(duì)應(yīng)的內(nèi)存地址,你想把SPI的接收發(fā)送數(shù)據(jù)存在哪個(gè)變量,就將對(duì)應(yīng)變量的地址賦給DMA存儲(chǔ)器地址寄存器。如u8 SPI1_TX_Buff的地址是(u32)&SPI1_TX_Buff;u8 SPI1_TX_Buff[512]的地址是(u32)SPI1_TX_Buff。
(3)DMA循環(huán)模式:有些資料會(huì)譯為DMA的循環(huán)緩存模式,我覺得不太準(zhǔn)確,這里循環(huán)的意思是指DMA的傳輸數(shù)量計(jì)數(shù)器會(huì)重置初值,由于DMA每傳一個(gè)數(shù)據(jù),傳輸數(shù)量計(jì)數(shù)器減一,只有在傳輸數(shù)量計(jì)數(shù)器的值不為零時(shí),才會(huì)響應(yīng)請(qǐng)求。在循環(huán)模式下,當(dāng)傳輸計(jì)數(shù)器的值減為0后,會(huì)重新裝載;而內(nèi)存(緩存)地址則不管循環(huán)非循環(huán)模式,都會(huì)在每次傳輸完成后重置為基地址。所以,如果我們把DMA設(shè)置會(huì)正常模式,那么在下次傳輸前,只需對(duì)DMA的傳輸數(shù)量計(jì)數(shù)器重新寫入就行。
循環(huán)模式一般用于數(shù)據(jù)更新,比如ADC采用需要不停更新數(shù)據(jù)。
(4)DMA的外設(shè)地址:正點(diǎn)原子的串口DMA實(shí)驗(yàn)中,在寫外設(shè)地址時(shí),都會(huì)用一個(gè)變量緩存再寫入,否則程序就運(yùn)行不正確,他也不知道為什么,而ST庫函數(shù)的example中對(duì)于外設(shè)地址也都是重新define的,所以外設(shè)地址最好還是采用#define SPI1_DR_Addr ( (u32)0x4001300C )定義的好。
至于外設(shè)地址,可以先從STM32的用戶手冊(cè)“2.3存儲(chǔ)器映像”得到起始地址+對(duì)應(yīng)外設(shè)所在目錄的“寄存器地址映像”標(biāo)識(shí)的偏移地址。例如:從“2.3存儲(chǔ)器映像”得到SPI1起始地址0x40013000,從SPI所在目錄的“寄存器地址映像”得到SPI->DR的偏移量為0x0C,那么SPI1_DR_Addr就是0x4001300C;
(5)DMA通道開啟順序:按照下圖的數(shù)字序號(hào)依次開啟,才能確保數(shù)據(jù)正確發(fā)送。比如①的SPI2_TX_Buff對(duì)應(yīng)的是DMA通道5.
(6)正常模式的第二次發(fā)送:DMA發(fā)送的時(shí)候只需使能DMA就可以開始傳送,但是第二次傳送之前,需要進(jìn)行以下步驟:
1、關(guān)閉DMA通道;
2、清除DMA傳輸完成標(biāo)志以及重置CNDTR傳輸數(shù)量計(jì)數(shù)器;
3、開啟DMA通道,等待傳輸完成。
四、實(shí)驗(yàn)結(jié)果
利用SPI1和SPI2進(jìn)行兩次數(shù)據(jù)傳輸,并比較SPI1_RX與SPI2_TX,SPI2_RX與SPI1_TX,數(shù)據(jù)相同點(diǎn)亮LED燈。
在某個(gè)論壇看到有人說把SPI的速度設(shè)置為2分頻傳輸數(shù)據(jù)不正確,分析原因是DMA反應(yīng)不過來。我也試了一下,傳輸正常,數(shù)據(jù)正確。(SPI傳輸速率是用JLINK仿真查看寄存器的)
哦,對(duì)了,期間還吃過一個(gè)虧,害我調(diào)了好久,就是下面的語句:
while( ( DMA1->ISR & (1<<17) ) == 0 ) ; //等待通道5傳輸完成
我寫成:
while( DMA1->ISR & (1<<17) == 0 ) ; //等待通道5傳輸完成
由于“==”的優(yōu)先級(jí)比“&”高,所以會(huì)先執(zhí)行“(1<<17) == 0”,結(jié)果是0,再與上DMA1->ISR,那么相當(dāng)于while直接跳過了,讀不到數(shù)據(jù)!很低級(jí)的錯(cuò)誤!所以提醒后來者,看起來可加可不加的括號(hào),還是要加上去的好!
還有一個(gè)問題,一直在想DMA傳輸,那么硬件怎么認(rèn)為一次傳輸?shù)慕Y(jié)束而停止以及怎樣才能開啟新一次的傳輸。我覺得最關(guān)鍵就是DMA的傳輸數(shù)量計(jì)數(shù)器以及DMA的傳輸完成標(biāo)志。只要DMA的計(jì)數(shù)器不為零,就能響應(yīng)請(qǐng)求傳輸,此時(shí)就算傳輸完成標(biāo)志置位,也能進(jìn)行DMA響應(yīng),只不過你不知道什么時(shí)候完成罷了。所以每次傳輸開始前,程序需要清除標(biāo)志位并檢測(cè)到該標(biāo)志位置位,才知道一次傳輸是否完成!
續(xù):終于把SPI的DMA弄完了,實(shí)現(xiàn)了連續(xù)發(fā)送和讀取的功能,DMA開辟512字節(jié)的數(shù)組作為內(nèi)存存儲(chǔ)數(shù)據(jù)(所以連續(xù)發(fā)送最大的數(shù)據(jù)量也是512,當(dāng)然可以在宏定義里面更改),通過num控制要寫入或讀入的數(shù)據(jù)量,源代碼中有3個(gè)函數(shù),一個(gè)函數(shù)是讀寫一體的,一個(gè)函數(shù)是只發(fā)送模式,一個(gè)函數(shù)是只接收模式,都通過測(cè)試。唯一的缺陷就是沒有進(jìn)行錯(cuò)誤檢測(cè),特別說明一下,我把清標(biāo)志位是放在函數(shù)前面而不是函數(shù)后面,就是想函數(shù)執(zhí)行完,標(biāo)志位依然還在,我們可以以此來判斷是否有錯(cuò)誤。在這里和大家分享一下小經(jīng)驗(yàn)。
(1)怎么測(cè)試?最好的測(cè)試方法我覺得就是雙機(jī)通訊了,由于實(shí)驗(yàn)室資源比較好,所以我得以有兩個(gè)STM32(非MiniSTM32,用的是AG嵌入式開發(fā)板)進(jìn)行測(cè)試,所以以上代碼都是通過雙擊測(cè)試的,不過我只整理了SPI1主機(jī)源代碼,需要的自己稍微改一下就可以,程序中有注釋!
(2)用雙機(jī)測(cè)試的時(shí)候,剛開始我沒有共地,導(dǎo)致數(shù)據(jù)可以接收,但是數(shù)據(jù)錯(cuò)誤!所以緊記,當(dāng)你使用兩個(gè)器件通訊或交互時(shí),一定要先檢查兩個(gè)器件是否共地,甚至共源!
(3)如果只有一個(gè)STM32其實(shí)也可以測(cè)試,就是把MISO和MOSI短接,但這個(gè)測(cè)試方法,用來測(cè)試SPI1_ReceiveSendByte(u16 num)就比較方便,用來測(cè)試只發(fā)送和只接收模式就需要改一下函數(shù)咯。
(4)弄了這么久的SPI_DMA,也不知道用處大不大,總之弄完了,呵呵,也算比較了解SPI總線和DMA了,接下來想試試原子哥的新的SD卡函數(shù),原來AG嵌入式開發(fā)板也是移植原子哥的舊版,也是有些卡初始化失敗,我還以為是我的卡有問題呢?還有就是文件系統(tǒng),前陣子只弄了基本的讀寫,準(zhǔn)備把FATFS文件系統(tǒng)寫得完善一點(diǎn)~
最后,附上源代碼。(使用的不是MiniSTM32,所以大家在測(cè)試時(shí)只需要改一下LED驅(qū)動(dòng)。)
第一個(gè)源代碼是基礎(chǔ)的,實(shí)現(xiàn)一個(gè)字節(jié)在SPI1&SPI2的傳送;
第二個(gè)是函數(shù)化的代碼咯,發(fā)送隨意數(shù)量的8bit數(shù)據(jù),數(shù)量小于512;
評(píng)論