:從時鐘極性與寄存器到代碼)
簡介面向DSP2812初學者的SPI通信完整工程資源以“EX04_SPI_LED”實例為主線講解SPI協(xié)議、硬件資源、寄存器配置和驅(qū)動LED亮度控制的實現(xiàn)方法。資源包含88個文件以C源碼、頭文件和工程配置文件為主另有obj/out/map等編譯產(chǎn)物涵蓋DSP28_Spi.c、main.c、SPI_LED.pjt等關(guān)鍵文件壓縮包僅358KB便于快速下載和導入CCS工程對照學習。內(nèi)容覆蓋SPI四種模式選擇、時鐘分頻、片選管理、收發(fā)寄存器及中斷處理等核心環(huán)節(jié)并配有可運行的LED驅(qū)動示例。通過查看源碼、構(gòu)建日志和調(diào)試輸出可以直觀理解DSP2812 SPI模塊的初始化流程與通信時序適合正在學習DSP外設(shè)開發(fā)或準備課程設(shè)計、綜合實驗的學生參考。已有261人學習資源結(jié)構(gòu)清晰是一份輕量實用、能直接上手調(diào)試的入門資料。 很多年前我第一次調(diào)DSP2812的SPI時還挺自信的結(jié)果一上電數(shù)據(jù)就亂飛查了半天發(fā)現(xiàn)是時鐘極性和相位搞錯了。那會兒網(wǎng)上資料少全靠啃英文手冊和一遍遍試寄存器?,F(xiàn)在回過頭看DSP2812的SPI模塊其實相當規(guī)整只要你把幾條核心配置鏈路搞清楚基本不會出大問題。這篇就把我實際用下來的經(jīng)驗和踩過的坑完整盤一遍從協(xié)議細節(jié)到寄存器配置再到完整的可運行代碼和排查思路希望能幫你少走彎路。1. 內(nèi)容整體設(shè)計與思路拆解1.1 先搞懂SPI這四種模式配置才不會懵SPI本質(zhì)上就是兩個移位寄存器之間互相倒數(shù)據(jù)主設(shè)備提供時鐘SCLK數(shù)據(jù)在MOSI和MISO兩條線上按位傳輸。DSP2812的SPI模塊支持四種工作模式區(qū)別就在于時鐘極性CPOL和時鐘相位CPHA的組合。所謂CPOL指的是空閑時SCLK的電平是高還是低CPHA則決定數(shù)據(jù)是在時鐘的上升沿還是下降沿被采樣。組合起來就是模式0到模式3這是整個SPI配置里最容易出錯的地方。很多初學者會直接照抄別人的初始化代碼但如果你外接的從設(shè)備手冊里寫的是“數(shù)據(jù)在SCLK上升沿采樣空閑時SCLK為低”那對應(yīng)的就是模式0。一旦配成模式1數(shù)據(jù)就會整體偏移半個周期表現(xiàn)為主機能發(fā)但收到的數(shù)據(jù)是錯位的甚至完全亂碼。DSP2812里控制這個的是SPICCR寄存器的CLKPOLARITY位和SPICTL寄存器的CLK_PHASE位兩個位配合起來決定你要的模式。我個人的習慣是拿到任何一個從設(shè)備第一件事就是翻它的數(shù)據(jù)手冊找到時序圖確認它到底屬于哪一種模式然后再去配置DSP2812。千萬不要想當然因為同一顆芯片的不同型號默認推薦模式可能都不一樣。比如常見的W25Q64 SPI Flash它支持模式0和模式3但很多SD卡模塊在SPI模式下又默認是模式0所以“先看手冊再接線配置”這個順序千萬不能省。1.2 為什么選SPI而不是I2C或UARTDSP2812同時有SCI也就是UART、SPI和McBSP等接口很多人會糾結(jié)用哪個。SPI的優(yōu)勢在于全雙工、速率高、時序簡單、沒有地址幀的概念主從之間就是純粹的“你給我一個字節(jié)我還你一個字節(jié)”。I2C雖然省引腳但協(xié)議復(fù)雜還要處理應(yīng)答信號UART雖然簡單但一般只能點對點而且速率受限。在DSP2812這種老資格的C2000芯片上SPI的時鐘源來自LSPCLK默認情況下LSPCLK是系統(tǒng)時鐘的一半。比如系統(tǒng)時鐘跑在150MHz那LSPCLK就是75MHzSPI再經(jīng)過分頻最高可以做到37.5MHz。這個速率對于驅(qū)動顯示屏、讀寫外部Flash、和ADC芯片通信完全是足夠了。而且SPI只要你線拉得夠短10cm以內(nèi)穩(wěn)定性非??煽?。當然SPI也有它的短板比如沒有應(yīng)答機制主設(shè)備發(fā)出數(shù)據(jù)后無法確認從設(shè)備是不是真的收到了通信距離一長就容易受到干擾熱詞里提到的“spi通信方式通信距離”就是這個痛點。常規(guī)經(jīng)驗是SPI適合板內(nèi)通信不要超過20cm線材要盡量短、盡量等長地線要確保共地。如果你是要做板間長距離通信更穩(wěn)妥的選擇是RS485或者CAN而不是硬把SPI線拉長。1.3 硬件片選與軟件片選DSP2812上怎么選SPI的從設(shè)備選擇信號CS/片選在DSP2812里比較特殊它有硬件片選和軟件片選兩種思路。硬件片選就是把SPISTE引腳復(fù)用成自動片選主設(shè)備在每次傳輸前會自動拉低這個引腳傳輸結(jié)束后再釋放。軟件片選則是你自己用任意一個GPIO在發(fā)送前手動拉低發(fā)送完再手動拉高。從我實際使用的體驗來看軟件片選在DSP2812上更靈活也更好用。原因很簡單硬件片選SPISTE引腳的時序在某些場景下不夠可控比如你要連續(xù)讀一塊Flash數(shù)據(jù)中間不能釋放片選但硬件自動片選可能在每字節(jié)之間都會有個拉高再拉低的動作直接導致通信失敗。軟件片選就沒有這個問題你想什么時候拉低、什么時候釋放完全由你的代碼控制。另外如果你的板子上有多片SPI從設(shè)備軟件片選是唯一合理的選擇——每片分配一個GPIO即可互不干擾。DSP2812的GPIO本身有限但SPI從設(shè)備通常也就是兩三個每個片選占用一個普通IO還是很劃算的。接線方式就是用GPIO去拉CS和真正復(fù)用SPISTE并沒有什么性能差異。2. 核心細節(jié)解析與實操要點2.1 模塊寄存器速查照著配不會漏DSP2812的SPI寄存器數(shù)量不算多但在配置的時候一定要按順序來。SPICCR是控制寄存器主要負責軟件復(fù)位、時鐘極性、數(shù)據(jù)字長這些SPICTL是控制寄存器2主要管使能、時鐘相位、中斷方式等SPIBRR是波特率寄存器SPISTS是狀態(tài)寄存器里面有RXINT_FLAG、TX_INT_FLAG等標志位SPIRXBUF和SPITXBUF是接收和發(fā)送緩沖區(qū)。很多人在初始化時犯的錯誤是改了SPICCR但忘了把SPI拉出復(fù)位狀態(tài)。SPICCR的SPI_SWRESET位默認是0也就是處于復(fù)位狀態(tài)所有配置都寫不進去。正確的順序是先配置其他位最后把SPI_SWRESET置1讓模塊退出復(fù)位開始工作。如果你先置1再改別的寄存器輕則配置不生效重則產(chǎn)生不可預(yù)期的時序。還有一點值得注意SPICCR的SPI_CHAR位決定字符長度支持1到16位但不建議配成奇奇怪怪的非8位值除非你非常清楚從設(shè)備的協(xié)議要求。DSP2812的SPI硬件會把接收數(shù)據(jù)放在SPIRXBUF里發(fā)送時你往SPITXBUF寫數(shù)據(jù)硬件會自動啟動傳輸傳輸完成后會置位相關(guān)標志。有些人的代碼死循環(huán)等標志等不到多半就是忘了把數(shù)據(jù)先寫進緩沖區(qū)或者SPI沒退出復(fù)位。建議配置完寄存器之后先回讀一遍確認這一步能省掉后面很多排查時間。2.2 時鐘頻率計算別把分頻系數(shù)算錯了DSP2812的SPI波特率不是直接填一個頻率值而是通過SPIBRR寄存器設(shè)定分頻系數(shù)。SPI時鐘源是LSPCLK默認等于系統(tǒng)時鐘除以2。如果你想讓SPI工作在比較低的速率可以直接用LSPCLK除以(SPIBRR1)如果希望高速則可以繞過SPIBRR直接在SPICCR里設(shè)置一種特殊的高速模式此時SPI時鐘直接等于LSPCLK。舉個例子系統(tǒng)時鐘跑150MHzLSPCLK為75MHz。你想得到一個約1MHz的SPI時鐘那SPIBRR的值就應(yīng)該是75-174波特率等于75MHz/(741)1MHz。這里有個容易踩坑的點SPIBRR寄存器只有8位最大只能表示255所以最低波特率也就被限制在75MHz/256大概293kHz。如果你的從設(shè)備要求更低比如有些SD卡初始化時要求不超過400kHz那你就得考慮把LSPCLK降下來或者在SPI配置里選擇更低的輸入時鐘。如果想驗證配置對不對最直接的辦法是把SPICLK引腳接示波器看波形或者用兩個GPIO模擬一個簡單的延時翻轉(zhuǎn)程序來確認LSPCLK的實際值。總之把分頻公式寫在注釋里后續(xù)改時鐘頻率的時候一定要同步修改SPIBRR否則就會出現(xiàn)通信超時或數(shù)據(jù)錯亂。2.3 數(shù)據(jù)幀長度和中斷標志的配合DSP2812的SPI支持1到16位的數(shù)據(jù)長度這在實際項目中其實很有用。很多傳感器芯片一次要讀12位或14位數(shù)據(jù)如果你只能用8位模式那就得拆成兩個字節(jié)來處理比較麻煩。DSP2812允許你把SPI_CHAR設(shè)成你想要的位數(shù)接收數(shù)據(jù)會存放在SPIRXBUF的低位高位自動補零。但在16位模式下有一個細節(jié)數(shù)據(jù)是右對齊的也就是說你發(fā)送的時候要手動把數(shù)據(jù)挪到低8位或低12位從設(shè)備返回的數(shù)據(jù)也一樣需要你在軟件里做掩碼處理。另外中斷標志在不同數(shù)據(jù)長度下行為稍有不同在8位模式下每次接收一個字節(jié)就會置一次RXINT_FLAG在16位模式下要接收完16位才會置位。這個差別會直接影響你的狀態(tài)機設(shè)計如果你的代碼里用的是8位假設(shè)切換到16位模式后整個流程都會亂。我一般建議如果不是特別需要盡量保持8位模式把多字節(jié)協(xié)議放在軟件層組合這樣代碼可讀性好也不容易出錯。如果確實需要16位務(wù)必在注釋里寫清楚當前SPI被配置成了幾位避免后面維護時踩坑。3. 實操過程與核心環(huán)節(jié)實現(xiàn)3.1 完整初始化代碼可直接抄作業(yè)下面是一段我在工程中實際用過的DSP2812 SPI初始化代碼配置為SPI主模式、模式0、8位數(shù)據(jù)波特率約1MHz使用GPIO軟件片選。整個初始化流程包含了GPIO復(fù)用配置、SPI寄存器初始化、退出復(fù)位三步void SPI_Init(void) { // 1. GPIO復(fù)用配置SPICLK、SPISIMO、SPISOMI GpioMuxRegs.GPFMUX.all | 0x000F; // SPICLK - GPIOF0, SPISIMO - GPIOF1, SPISOMI - GPIOF2 // 注意GPIOF3 是 SPISTE我們不用硬件片選所以不要把它配置成SPI功能 // 2. 軟件片選引腳配置例如使用GPIOF4作為CS GpioMuxRegs.GPFMUX.all 0xFFEF; // GPIOF4作為普通GPIO GpioMuxRegs.GPFDIR.all | 0x0010; // 配置為輸出 // 3. 先拉高片選表示當前沒有選中任何從設(shè)備 GpioDataRegs.GPFSET.all 0x0010; // 4. SPI初始化寄存器 SpiaRegs.SPICCR.all 0x0007; // 8位字符時鐘極性為0軟件復(fù)位狀態(tài) SpiaRegs.SPICTL.all 0x0006; // 主模式時鐘相位為0禁止中斷 SpiaRegs.SPIBRR 74; // 波特率 LSPCLK / (741)LSPCLK75MHz時約1MHz // 5. 退出復(fù)位使能SPI SpiaRegs.SPICCR.all 0x0087; // SPI_SWRESET1其他設(shè)置保留 // 6. 清除標志位 SpiaRegs.SPISTS.all 0x0000; }這段代碼的核心就是先配置GPIO復(fù)用再配SPI寄存器最后把SPI_SWREST置1。不要小看這個順序如果GPIO沒有正確復(fù)用為SPI功能后面無論怎么配置SPI寄存器都沒有信號輸出。另一個容易漏的是SPICTL里的MASTER_SLAVE位如果不置1芯片會默認工作在從模式結(jié)果就是時鐘永遠出不來。3.2 主模式收發(fā)一個字節(jié)的實現(xiàn)SPI是全雙工的所以發(fā)送一個字節(jié)的同時一定會收到一個字節(jié)。即使你只關(guān)心發(fā)送也要把接收寄存器讀走否則接收緩沖區(qū)滿了之后后續(xù)的數(shù)據(jù)會丟失。下面是一個簡單的單字節(jié)收發(fā)函數(shù)Uint16 SPI_SendByte(Uint16 data) { // 等待發(fā)送緩沖區(qū)為空 while (SpiaRegs.SPISTS.bit.TX_INT_FLAG 0) {} // 發(fā)送數(shù)據(jù) SpiaRegs.SPITXBUF data; // 等待接收完成 while (SpiaRegs.SPISTS.bit.RX_INT_FLAG 0) {} // 返回接收到的數(shù)據(jù) return SpiaRegs.SPIRXBUF; }這段代碼邏輯上沒問題但實際項目中我遇到過一個問題如果SPI時鐘速率很高發(fā)送完成后RX_INT_FLAG可能還沒及時置位導致循環(huán)多等了一會兒影響實時性。另一種情況是某些從設(shè)備在CS拉低后需要一點時間準備數(shù)據(jù)所以發(fā)送完第一個字節(jié)后收到的可能是無效數(shù)據(jù)。此時你先拉低CS再發(fā)送一個空字節(jié)比如0x00來觸發(fā)從設(shè)備返回數(shù)據(jù)然后把讀到的值丟棄再從第二個字節(jié)開始取有效數(shù)據(jù)這樣更穩(wěn)定。如果你需要批量收發(fā)數(shù)據(jù)就用狀態(tài)機來管理先拉低片選、然后循環(huán)調(diào)用SPI_SendByte、最后拉高片選。記住每次通信結(jié)束后要延時一小段時間具體值看從設(shè)備手冊讓從設(shè)備完成內(nèi)部處理再開始下一次操作。對于W25Q64這類Flash寫命令之后尤其要等內(nèi)部寫周期完成這一段經(jīng)驗也是我踩過坑之后才總結(jié)下來的。3.3 讀外部Flash的實際案例把流程完整走一遍以W25Q64為例讀數(shù)據(jù)的流程是拉低CS發(fā)送讀命令0x03再發(fā)送24位地址高字節(jié)在前然后持續(xù)發(fā)送空字節(jié)讀取數(shù)據(jù)讀完最后一個字節(jié)后拉高CS。用DSP2812的代碼大致是這樣void SPI_ReadFlash(Uint32 addr, Uint8 *buf, Uint16 len) { Uint16 i; // 選中Flash GpioDataRegs.GPFCLEAR.all 0x0010; // 發(fā)送讀命令 SPI_SendByte(0x03); // 發(fā)送24位地址 SPI_SendByte((addr 16) 0xFF); SPI_SendByte((addr 8) 0xFF); SPI_SendByte(addr 0xFF); // 連續(xù)讀取數(shù)據(jù) for (i 0; i len; i) { buf[i] (Uint8)SPI_SendByte(0xFF); } // 釋放片選 GpioDataRegs.GPFSET.all 0x0010; }這段代碼里最關(guān)鍵的就是SPI_SendByte(0xFF)這一步因為SPI是雙向的主設(shè)備要產(chǎn)生時鐘就必須持續(xù)往發(fā)送寄存器里寫數(shù)據(jù)哪怕你只關(guān)心接收內(nèi)容。很多人一開始不明白“讀Flash為什么要發(fā)數(shù)據(jù)”其實就是這個原因。還有一點是地址發(fā)送順序W25Q64的命令格式是高字節(jié)在前不能搞反否則讀到的數(shù)據(jù)永遠是錯的。這個流程同樣適用于讀寫其他SPI從設(shè)備比如讀取ADC采樣結(jié)果、配置傳感器寄存器等無非是把命令和地址改成對應(yīng)芯片手冊里的格式。多加幾次實踐之后你就會發(fā)現(xiàn)SPI調(diào)試的核心就一句話把時鐘搞對把數(shù)據(jù)對齊片選時序按手冊走。4. 常見問題與排查技巧實錄4.1 只能發(fā)送不能接收多半是引腳配置錯了我剛接觸DSP2812的SPI時遇到的最詭異問題就是示波器上能看到MOSI有波形但MISO上永遠是高電平收到的數(shù)據(jù)全是0xFF。排查了很久最后發(fā)現(xiàn)是GPIO復(fù)用沒配好SPISOMI引腳還在普通GPIO輸入模式信號根本進不了SPI模塊。DSP2812的GPIO復(fù)用寄存器比較隱蔽很多時候你只配了SPICLK和SPISIMO忘了SPISOMI結(jié)果發(fā)送正常、接收異常。所以初始化時務(wù)必把三個引腳全部配置為SPI功能并且確認GPIO方向寄存器不會沖突。如果你用了軟件片選還要額外確認片選引腳沒有和SPI功能引腳重疊。除此之外還有一個是硬件層面的問題兩個設(shè)備之間的地線沒接好。SPI不像RS485那樣有共模抑制能力如果主從設(shè)備不共地MISO線上的信號參考電壓就不一致大概率收不到正確的數(shù)據(jù)。所以出現(xiàn)通信問題的時候先檢查物理連接再查代碼順序不要反了。4.2 數(shù)據(jù)錯位或亂碼排查時鐘極性和相位的順序數(shù)據(jù)錯位是最典型的SPI故障表現(xiàn)為你發(fā)出正確命令后從設(shè)備返回的數(shù)據(jù)雖然“有點像”但字節(jié)順序?qū)Σ簧匣蛘呙總€字節(jié)都差一位。這種問題基本可以鎖定在時鐘極性和相位的配置上。調(diào)試手法很簡單把示波器探頭放在SCLK和MOSI上觀察空閑時段SCLK的電平以及數(shù)據(jù)變化點是在時鐘上升沿還是下降沿然后和從設(shè)備手冊的時序圖對比。比如你的CS拉低后SCLK一直是低說明CPOL0數(shù)據(jù)在SCLK上升沿被采樣說明CPHA0那正好是模式0。如果手冊里寫的是從模式1你卻在初始化里配成模式0那通信必然錯。有時候錯位還跟數(shù)據(jù)位長度相關(guān)。比如你把SPI_CHAR配置成了16位但代碼里卻按字節(jié)往里寫那數(shù)據(jù)就會兩個字節(jié)黏在一起看起來就是亂碼。遇到這種情況先回讀SPICCR確認配置再考慮是不是代碼邏輯問題。4.3 波特率太高導致的不穩(wěn)定降速就正常了SPI速率調(diào)高之后偶爾會出現(xiàn)“十次通信里失敗一兩次”的隨機故障。這種隨機問題最煩人因為不是必現(xiàn)但一旦出現(xiàn)就會打亂整個系統(tǒng)。我遇到過的情況是SPI速率跑到20MHz以上后線材稍微長一點就會出現(xiàn)偶發(fā)錯誤降速到10MHz以下就完全穩(wěn)定。排查方法是用示波器測量SCLK的波形看是否存在明顯的過沖和振鈴。如果信號邊沿不夠陡峭或者出現(xiàn)臺階那就說明信號完整性已經(jīng)出問題了。解決辦法有三個方向一是降低SPI時鐘頻率二是縮短連接線長度盡量用杜邦線的話不要超過10cm三是在MISO、MOSI、SCLK上串33歐姆左右的電阻來抑制過沖。另外如果你的主控DSP2812和從設(shè)備之間的電平不匹配比如DSP是3.3V從設(shè)備是5V一定要加電平轉(zhuǎn)換芯片或者用開漏加外部上拉的方式絕對不能直接用5V信號去灌DSP引腳。這是硬件設(shè)計層面的紅線一旦燒了引腳就不好玩了。4.4 DMA和SPI配合的注意事項DSP2812的SPI模塊本身不帶FIFO更別說DMA了和STM32那種帶DMA的SPI體驗完全不同。那如果要做高速連續(xù)傳輸怎么辦我的經(jīng)驗是用SPI中斷每收到一個字節(jié)就觸發(fā)一次中斷在中斷里讀走數(shù)據(jù)和發(fā)送下一個字節(jié)。但DSP2812的中斷響應(yīng)是有延遲的SPI速率如果太高字節(jié)之間的間隔不夠中斷響應(yīng)時間就可能丟數(shù)據(jù)。所以如果你要在DSP2812上用SPI做大容量數(shù)據(jù)傳輸建議把SPI速率控制在2MHz以下或者采用“查詢方式定時啟動”的模式來降低中斷頻率。如果一定要高速可以考慮用McBSP模塊的DMA能力但那樣的話代碼復(fù)雜度會高很多一般不推薦新手去碰。4.5 常見問題速查表現(xiàn)象可能原因解決辦法SCLK無波形SPI未退出復(fù)位、GPIO未復(fù)用、主從模式配錯檢查SPICCR的SWRESET位、GPF復(fù)用、MASTER_SLAVE位發(fā)送正常接收全FFSPISOMI引腳未復(fù)用、MISO接線錯誤、未共地配置SPISOMI引腳復(fù)用檢查硬件連接數(shù)據(jù)錯位/亂碼CPOL/CPHA配錯、數(shù)據(jù)長度不對對照手冊確認模式回讀SPICCR確認配置偶發(fā)通信失敗速率太快、線太長、信號過沖降速、縮短線長、串阻尼電阻讀Flash讀到全FF片選時序不對、命令格式錯誤、Flash內(nèi)部忙核對片選拉低/拉高時序檢查命令順序等待寫周期結(jié)束每次排查的時候先看物理層再看寄存器配置最后才懷疑代碼邏輯。SPI這東西最大的特點就是邏輯簡單、物理敏感多數(shù)問題其實都出在引腳復(fù)用和片選時序上。我在實際項目里最后養(yǎng)成的一個習慣是每次初始化完SPI之后先從設(shè)備里讀一個固定ID寄存器比如Flash的JEDEC ID確認能讀到正確的ID再繼續(xù)后續(xù)業(yè)務(wù)邏輯。這相當于一個硬件的握手自檢能在問題發(fā)生的瞬間就把故障范圍鎖定在SPI通信環(huán)節(jié)省下來的調(diào)試時間絕對不是一點半點。如果你手里的從設(shè)備沒有ID寄存器那就用一個固定值的寄存器做同樣的驗證辦法總比問題多。本文還有配套的精品資源點擊獲取