
簡介STM32F103VE驅動2.8寸ILI9341 LCD顯示圖片的完整工程源碼面向嵌入式入門開發(fā)者及需要快速實現(xiàn)屏幕顯示的項目人員。工程基于ARM Cortex-M3內核通過SPI接口與ILI9341通信包含LCD初始化、清屏、畫點、圖片顯示等封裝函數(shù)并針對正點原子34針2.8寸屏完成適配。壓縮包共413個文件以h頭文件、c源碼、d依賴文件、o目標文件及crf編譯中間產(chǎn)物為主其中h和c覆蓋全部底層驅動與上層調用邏輯uvprojx為Keil工程配置可直接打開hex為編譯后的燒錄文件另含jpg測試圖片與編譯清理腳本整體約11.94MB目錄結構清晰便于按模塊查閱。已有1531人瀏覽學習。配合Image2LCD工具可將圖片轉換為ILI9341可直接顯示的數(shù)據(jù)格式幫助理解像素編碼與SPI傳輸流程代碼風格簡潔、函數(shù)封裝完善可直接編譯燒錄驗證也可作為二次開發(fā)模板。無論你想掌握LCD初始化時序還是需要快速點亮屏幕這份資源都能提供可靠參考。 前一陣子做一個小項目需要在本地設備上顯示一張產(chǎn)品示意圖。我翻出手頭這塊吃灰挺久的2.8寸ILI9341 LCD屏配上STM32F103VE目標很明確把一張240x320的圖片老老實實顯示出來。原本以為跑個官方例程、換個數(shù)組就能收工結果從接線到取模、從初始化到燒錄硬是踩了一圈坑才把屏幕點亮。這篇文章就把我這次完整的調試過程寫出來包括硬件接線、初始化序列、圖片數(shù)據(jù)制作、燒錄報錯排查這幾個環(huán)節(jié)給同樣想用STM32F103VE驅動ILI9341顯示圖片的朋友一個可以直接照著做的參考。1. 為什么最終選了STM32F103VE 2.8寸ILI9341這個組合1.1 ILI9341這塊屏的參數(shù)和接口類型2.8寸ILI9341是市面上最常見、資料最多的小尺寸TFT屏之一分辨率240x320驅動芯片是ILITEK的ILI9341支持16位/18位真彩色。常見的模塊會引出SPI接口或者8080并口我手里這塊只接了SPI引腳的版本總共8個引腳VCC、GND、CS、SCLK、MOSI、DC、RST、BLK。選它最核心的原因就三個一是分辨率在小屏里夠看能放下一張帶標題的示意圖二是SPI接口節(jié)省IOSTM32F103VE引腳雖然多但掛了一堆傳感器之后也未必夠用三是ILI9341的資料和代碼極其成熟不管是正點原子的代碼還是網(wǎng)上各路開源工程初始化序列都是現(xiàn)成的出問題也好查。1.2 為什么是VE而不是C8T6Flash容量是硬門檻很多人會問都是F103系列為什么偏偏用帶“VE”后綴的型號關鍵差異在Flash和RAMSTM32F103C8T6只有64KB Flash和20KB RAM而STM32F103VE有512KB Flash和64KB RAM。這個差別在顯示圖片的時候是致命的。一張240x320的16位色圖片裸數(shù)據(jù)是240×320×2153600字節(jié)也就是150KB。64KB Flash的C8T6連一張圖都塞不下更別說還要放固件代碼。VE的512KB Flash扣掉固件占用大概能放兩三張整幅圖片RAM也能輕松開幀緩沖。如果你只是顯示字符和色塊C8T6完全夠用但標題是“顯示圖片”那就老老實實選VE這個級別。另外F103VE有FSMC外設如果后續(xù)想升級成8080并口屏FSMC可以直接映射LCD為內存區(qū)域寫像素就像寫普通變量一樣快。這次雖然用的是SPI模塊但芯片選型上已經(jīng)把這個擴展余地留好了。2. 硬件接線影響屏穩(wěn)定性的幾個“小”細節(jié)2.1 引腳分配與SPI外設的對應關系接線這部分最容易翻車尤其是把軟件IIC的思路帶到SPI上來隨意接引腳導致后面調不通。STM32F103VE的SPI1外設默認引腳是PA5SCK、PA6MISO、PA7MOSINSS片選可以軟件控制我建議直接用一個普通GPIO來控制CS方便后續(xù)掛多個SPI設備。我用的具體接法如下LCD引腳STM32F103VE引腳說明VCC3.3V模塊供電GNDGND共地CSPA4片選軟件控制低電平有效SCLKPA5SPI1_SCK硬件SPI時鐘MOSIPA7SPI1_MOSI主發(fā)從收DCPA2數(shù)據(jù)/命令選擇低電平命令高電平數(shù)據(jù)RSTPA1復位信號低電平復位BLKPA0背光控制高電平點亮可接PWM調亮度這里有個細節(jié)必須提DC引腳必須選一個普通的、沒有復用沖突的GPIO因為它和SPI硬件無關純粹是給ILI9341發(fā)送一個電平狀態(tài)告訴它當前字節(jié)是命令還是數(shù)據(jù)。很多人一開始把DC接到PA4這種引腳上等后面想用硬件NSS才發(fā)現(xiàn)沖突。所以規(guī)劃引腳時先把芯片外設的固定引腳畫出來再分配GPIO別一開始就亂點鴛鴦譜。2.2 背光、去耦電容和引腳上拉的隱性作用BLK背光引腳不要直接懸空。有些模塊內部沒加上拉你不控制它背光默認就不亮很容易誤判成屏幕壞了。我用的模塊把BLK接一個限流電阻到3.3V就能常亮但更推薦的方式是把BLK接到PA0然后軟件里輸出高電平后面想用PWM調亮度也方便。電源這一塊是很多新手的盲區(qū)。LCD模塊內部有電荷泵電路給玻璃基板供電瞬間電流波動比想象中大。如果直接用杜邦線從開發(fā)板的3.3V引腳取電在刷屏瞬間SPI數(shù)據(jù)傳輸導致電流抖動屏幕就可能出現(xiàn)水波紋甚至閃白。正確做法是在LCD模塊的VCC和GND之間就近并聯(lián)一個10uF電解電容和一個100nF陶瓷電容。這就是熱詞里提到的“l(fā)cd極化避免”——電源極性不能接反電解電容正負極也不能顛倒去耦要靠近模塊供電端而不是靠近開發(fā)板端。至于4*4鍵盤為什么有人說要上拉道理和LCD控制引腳一樣的GPIO在MCU復位的瞬間處于高阻態(tài)如果不外部上拉DC和CS信號會亂飄屏可能在上電瞬間收到垃圾命令閃一下白屏。我給CS、DC、RST三個引腳都加了10K上拉到3.3V復位期間保持確定電平實測白屏問題基本消失。3. 初始化序列拆解點亮屏幕前ILI9341做了什么3.1 從復位到Sleep Out屏的啟動狀態(tài)機ILI9341上電之后不是馬上就能顯示它內部有一個比較嚴謹?shù)臓顟B(tài)機硬件復位 - 等待電源穩(wěn)定 - Sleep Out退出睡眠- 等待內部DC-DC穩(wěn)定 - 設置像素格式和掃描方向 - Display On。很多人直接把網(wǎng)上抄的初始化數(shù)組往SPI里一丟屏幕上出來東西了就完事??梢坏┢敛涣辆屯耆恢缽哪睦锱挪椤N医ㄗh至少把初始化序列按功能分段理解這樣出了問題你能定位到是供電問題、時序問題還是命令參數(shù)問題。我用的核心初始化流程簡化版如下static void LCD_Init(void) { // 1. 硬件復位 LCD_RST_L(); HAL_Delay(50); LCD_RST_H(); HAL_Delay(120); // 2. 軟件復位 LCD_Write_Cmd(0x01); // Software Reset HAL_Delay(120); // 3. 退出睡眠 LCD_Write_Cmd(0x11); // Sleep Out HAL_Delay(120); // 4. 像素格式16bit LCD_Write_Cmd(0x3A); // Pixel Format Set LCD_Write_Data(0x55); // 16位真彩色RGB565 // 5. 內存訪問控制設置掃描方向 LCD_Write_Cmd(0x36); // Memory Access Control LCD_Write_Data(0x08); // 正常方向BGR順序調整后為0x08 // 6. 顯示開 LCD_Write_Cmd(0x29); // Display ON HAL_Delay(20); }上面的0x11 Sleep Out比較關鍵。ILI9341上電默認是處于睡眠模式的你不發(fā)這條命令即使所有寄存器都配好了背光亮著屏幕也是一片白或者暗暗的。很多人第一次接屏看到白屏第一反應是SPI時序不對其實很可能就是忘了這步。3.2 關鍵命令顏色格式、掃描方向、窗口功能0x3A命令設置像素格式參數(shù)0x55表示16位色。這是顯示圖片的基礎后面的RGB565顏色數(shù)據(jù)能否正確解析全靠這一條。如果這里設置成18位色數(shù)據(jù)格式錯位圖片顏色就會完全錯亂。0x36命令控制掃描方向參數(shù)0x08是常用的橫屏/豎屏調整開關。這個命令實際上是控制ILI9341從哪個角開始刷新像素。默認從上到下、從左到右如果你的屏幕鏡像了或者反了調整這個字節(jié)就行不需要改數(shù)據(jù)數(shù)組。真正顯示圖片要理解的是“窗口功能”它由0x2A列地址和0x2B行地址兩條命令配合。窗口功能允許你指定屏幕上一個矩形區(qū)域之后所有的數(shù)據(jù)寫入只在這個窗口內生效。顯示圖片時我們先把窗口設成圖片左上角到右下角然后連續(xù)發(fā)送整張圖片數(shù)據(jù)ILI9341會自動依次填入這個矩形區(qū)域。這個機制特別像在Word里選中一塊區(qū)域再粘貼圖片區(qū)域選好了數(shù)據(jù)往里倒就行。4. 讓圖片上屏從BMP到RGB565數(shù)組的完整鏈路4.1 圖片預處理與取模工具的參數(shù)選擇LCD本身只認識像素點數(shù)據(jù)不認識JPG更不認識PNG。所以得先在電腦上把圖片轉換成C語言數(shù)組。我用的是Image2Lcd這個工具界面老但很好用。設置參數(shù)時要注意幾點輸出數(shù)據(jù)類型選“C語言數(shù)組”掃描方式選“水平掃描”輸出灰度選“16位真彩色”最大寬度和高度填240和320勾選“高位在前”還是“低位在前”取決于你代碼里怎么拼RGB565數(shù)據(jù)這里最坑的是字節(jié)序。RGB565一個像素占2字節(jié)比如紅色是0xF800在工具里可能會生成{0x00, 0xF8}或者{0xF8, 0x00}兩種排列。如果顯示出來每個顏色通道明顯反了紅藍互換就是字節(jié)序沒對上。我在代碼里統(tǒng)一按“高字節(jié)在前”處理數(shù)據(jù)直接按16位讀取實測正常。4.2 容量計算一張圖占多少Flash空間轉換完的數(shù)組很嚇人我用文本編輯器打開生成的.c文件一個240x320的數(shù)組有幾百行。一張圖153600字節(jié)是固定的這和內容無關因為每像素2字節(jié)不可能壓縮。這個數(shù)據(jù)量放在STM32F103VE的512KB Flash里問題不大但如果你的工程啟用了很多庫剩余空間可能不足編譯時就會報FLASH溢出。一個比較實用的檢查方法是編譯后在Map文件里看_ImageSize如果固件加上圖片數(shù)組超過512KB就要考慮砍掉一些不用的功能或者用外部SPI Flash存儲圖片。我的工程里放了2張圖一張是菜單背景一張是產(chǎn)品示意圖編譯后剩余Flash還有一百多KB整體穩(wěn)定。注意大數(shù)組要定義成const uint8_t image[] {}放到Flash里不要定義成局部數(shù)組或者全局普通數(shù)組否則會占掉珍貴的64KB RAM。4.3 顯示核心代碼設置窗口加連續(xù)寫顯存圖片數(shù)據(jù)顯示的核心邏輯其實就三步設置窗口、把顯存指針指到窗口起點、連續(xù)寫入數(shù)據(jù)。用SPI加DMA發(fā)送可以極大提升刷新速度。void LCD_ShowImage(uint16_t x, uint16_t y, uint16_t w, uint16_t h, const uint8_t *img) { uint32_t imgSize (uint32_t)w * h * 2; // 1. 設置窗口 LCD_Write_Cmd(0x2A); // 列地址 LCD_Write_Data(x 8); LCD_Write_Data(x 0xFF); LCD_Write_Data((x w - 1) 8); LCD_Write_Data((x w - 1) 0xFF); LCD_Write_Cmd(0x2B); // 行地址 LCD_Write_Data(y 8); LCD_Write_Data(y 0xFF); LCD_Write_Data((y h - 1) 8); LCD_Write_Data((y h - 1) 0xFF); // 2. 連續(xù)寫顯存 LCD_Write_Cmd(0x2C); // Memory Write // 3. 片選拉低后連續(xù)傳輸 LCD_CS_L(); HAL_SPI_Transmit_DMA(hspi1, (uint8_t *)img, imgSize); // 等待DMA完成或者用回調里拉高CS }這段代碼里有一個細節(jié)很多人會忽略DMA發(fā)送1024字節(jié)以上數(shù)據(jù)時SPI片選必須由軟件持續(xù)鎖住不能每個字節(jié)都拉高拉低。我之前寫過一份代碼在逐字節(jié)發(fā)送時每次傳輸完都拉高CS結果一刷整屏就花了因為ILI9341把一次窗口的數(shù)據(jù)截斷成了很多個不完整的傳輸。正確的流程是發(fā)完0x2C命令后保持CS拉低讓DMA把整塊數(shù)據(jù)一次推給屏幕全部結束后再拉高CS。實測整屏刷新時間從逐字節(jié)傳輸?shù)?秒多降到了200ms左右視覺上就是流暢了很多。5. 實測排坑device not found、花屏、顏色異常逐個擊破5.1 燒錄失敗device not found排查全過程代碼寫完信心滿滿去燒錄結果Keil直接彈了個報錯核心提示是error: device not found - device:vendor: stm32f103ve stmicroelectronics這個報錯的意思是調試器我用的是ST-Link沒有在SWD總線上發(fā)現(xiàn)STM32F103VE這顆芯片。排查鏈路我走了一遍按順序記錄一下幫大家省時間。第一步查接線。SWD只需要四根線SWDIO、SWCLK、GND、3.3V。很多人會漏接GNDST-Link和目標板不共地SWD時序就完全亂掉這是最常見的“device not found”原因。檢查之后確認接線沒問題。第二步查目標板供電。屏幕模塊直接從板上3.3V取電如果屏的電荷泵把電壓拉低芯片可能處于欠壓復位狀態(tài)自然找不到設備。我嘗試把LCD模塊供電斷開只用杜邦線給芯片供電再燒錄就成功了。這讓我意識到問題出在電源上而不是芯片壞了。第三步查復位狀態(tài)。如果RST引腳被外部電路持續(xù)拉低芯片一直處在復位狀態(tài)同樣找不到設備。用萬用表量RST引腳電壓正常應該在3.3V附近如果為0就要順著復位電路查。最終的處理方案是在ST-Link和開發(fā)板之間加了一根粗短的GND線和3.3V線降低線路阻抗。屏幕供電獨立從USB轉TTL的5V經(jīng)過一塊AMS1117降壓模塊取電不再和MCU搶電。這樣處理之后燒錄一次通過。后來看論壇上很多人也遇到過類似問題大部分都是供電不足引起的極少數(shù)是芯片讀保護鎖死需要先用ST-Link Utility解鎖。5.2 花屏與殘影從供電和SPI速率兩個方向查燒錄成功后第一次刷圖屏幕下半部分全是彩條上半部分正常。這個現(xiàn)象我第一反應是數(shù)據(jù)窗口設置不對但檢查半天沒發(fā)現(xiàn)邏輯錯誤。后來用邏輯分析儀抓了SPI信號發(fā)現(xiàn)波形很“毛糙”SCLK上升沿處有很多抖動。這就是典型的信號完整性問題因為杜邦線太長加上背光電流在公共地線上疊加了紋波把SPI時序搞亂了。解決方案有兩步。第一步在LCD的VCC和GND之間加了一個100uF電解電容效果立竿見影花紋區(qū)域縮小了第二步把SPI時鐘從18MHz降到9MHz整屏刷新率雖然低了一點但顯示徹底干凈了。feedthrough這個現(xiàn)象也值得提一下。LCD內部相鄰像素之間存在寄生電容當某一行數(shù)據(jù)切換時電壓會串擾到鄰近行表現(xiàn)為高對比度圖像邊緣出現(xiàn)殘影或拖影。這在低刷新率和信號不干凈時特別明顯。我的體會是如果屏幕顯示靜止圖片時邊緣有一圈淡色拖影優(yōu)先查電源紋波其次降低SPI速率實在不行再把窗口刷新改成逐行刷新。5.3 顏色偏色與掃描方向的判斷邏輯圖片顯示出來了但顏色明顯發(fā)藍發(fā)紅或者紅藍互換。這種情況基本可以鎖定在RGB565的字節(jié)序上。ILI9341命令0x36的BGR位會影響顏色通道的排列。默認情況下紅色在LCD上可能被解析成藍色。我在初始化里把0x36參數(shù)設為0x08這個值開啟了BGR順序。如果你發(fā)現(xiàn)紅藍完全互換就在0x08和0x28之間切換藍色問題立刻解決。還有一個容易忽略的問題圖片在電腦上看正常但屏幕上上下顛倒或者左右鏡像。原理不是取模出錯了而是0x36命令的掃描方向位沒配好。調整0x36參數(shù)參數(shù)值效果0x08默認方向從上到下從左到右0xC8上下顛倒0x28左右顛倒0xE8上下左右均顛倒如果你想固定一個方向又不想每次重新取模直接改這個參數(shù)就行比重新轉換圖片省事得多。5.4 復位瞬間閃白屏上拉電阻帶來的教訓整個項目調試到后期發(fā)現(xiàn)一個偶發(fā)問題MCU重新上電的瞬間屏幕會閃一下白屏然后正常顯示。一開始以為是初始化時序問題但每次打開電源都閃說明問題發(fā)生在上電早期。用示波器抓DC引腳的電平變化發(fā)現(xiàn)MCU復位期間DC引腳電壓呈鋸齒狀亂跳直到程序啟動后被GPIO配置拉穩(wěn)。IL9341在上電瞬間收到亂碼命令就會短暫進入異常狀態(tài)。解決方法是給DC、CS、RST三個引腳分別加10K上拉電阻到3.3V確保MCU復位時這些信號保持高電平屏不會誤觸發(fā)。這個思路和4*4鍵盤矩陣接上拉電阻是同一個道理GPIO在MCU復位瞬間不可控外部上拉可以提供確定的默認電平。如果你用的是現(xiàn)成開發(fā)板板上可能已經(jīng)帶了這些上拉電阻但如果是自己畫的板子或者飛線連接這一課一定要記住。文章寫到這我再補充一個實際操作中的小技巧SPI的MISO引腳在只讀驅動LCD時完全可以不接因為ILI9341的SPI讀功能基本用不上我們只關心寫數(shù)據(jù)。省掉MISO線可以減少杜邦線數(shù)量讓信號干擾少一個來源。別小看這一個小改動實測下來信號毛刺確實少了一些。還是那句老話LCD驅動本身的邏輯不難真正要花心思的往往是供電、接線和時序這些隱蔽環(huán)節(jié)。希望這篇文章能幫你少走一點彎路。本文還有配套的精品資源點擊獲取