|
Spi接口的接線,分四線制和三線制,據說SPI協議沒有確定的文字規定,只有實事的實現方案,而行業內的現狀則是眾說紛紜,我大概把它們分為兩類,一類是按實際用了幾條線實現傳輸,一類是按信號傳遞格式來劃分。具體到屏顯驅動來講,凡是接口有獨立的數據/命令(RS.DC.A0等叫法都有)接線的,屬于我們適用的范圍,具體用了幾條線,我數學不好,就不去數了。這不是說沒有數據/命令選擇線的不能用,反而是那種可能更有用這種驅動模式的需要,但我手里沒有這樣的屏,對不熟悉,沒驗證的東西,就不去評論了。
SPI接口的TFT彩屏在mcu工程里是常見的。其優點是接線方便,占用IO 口少,色彩豐富。缺點是速度會慢些,一方面是因為彩屏顯示需要的數據量大,單色屏一個字節可以包涵8個點的顯示信息,而彩屏一個點就需要二個字節的信息。另一方面是它屬于串口傳輸,并口傳輸一次可以送出8到16位信息,而串口一次只能送出一位信息,信息量大加傳輸慢,就成了SPI接口彩屏的一個短板。特別是在屏的顯示點數多時,尤其明顯。
記得DMA技術剛問世時是用在PC機上,當時經銷商把它當賣點,用戶則花錢買功能。只要花了錢,就能用上這個功能了。
但在mcu領域有些不同,DMA技術不僅需要芯片功能上支持,還需要技術人員愿意去用。一些學成的技術人員因為工作忙,面對的問題又能用傳統手段解決,使用DMA的動力就不足了。而新人由于感覺這東西有些難弄,也不愿用。
本文說的TFT彩屏SPI驅動的編程,是基于DMA功能的SPI彩屏驅動程序,因為SPI接口慢,所以使用DMA的意義更大。由于STC32G12K128芯片具有DAM功能,所以就以這個芯片為依托,寫了一組SPI接口的驅動程序,這個模式的程序,能加快刷屏速率,很大地提高芯片的工作效率。
下面先看一下SPI接口驅動中最基本的函數,數據傳送函數。分別是軟件模擬SPI傳送,硬件SPI數據傳送和基于SPI_DMA的數據傳送函數
第一個:軟件模擬SPI數據傳送函數
void transfer_data(unsigned int data1)//需要傳送命令時加DC=0
{
char i;
CS=0;
for(i=0;i<8;i++)
{
SCL=0;//sclk=0;
//delay(4);
if((data1&0x80)==0) SDA=0;//sid=1;
else SDA=1;//sid=0;
SCL=1;//sclk=1;
data1=data1<<=1;
// delay(4);
}
// CS=1;
}
第二個:硬件SPI數據傳送函數
void transfer_data(unsigned int data1)//需要傳送命令時加DC=0
{
CS=0;
SPSTAT=0xc0;
SPDAT=data1;
while(!SPIF);//等待發送完成
}
第三個:基于SPI_DMA的數據傳送函數
void transfer_data(unsigned int dat1)
{
CS=0;
DMASPI_initial();//這一句不是必須的,為了保險,我喜歡加上這一句
*(unsigned char *)(0x10000)=dat1;//待傳送數據送專用緩存區,就是buffer1
DMA_SPI_CR=0xc1;//開始執行,采用主機模式,并清空FIFO,
while(!(DMA_SPI_STA&0x01));
}
顯然,軟件模擬SPI傳送函數比較麻煩,速度也慢很多,但它的優點是對mcu硬件依賴少,基本可以在任何IO口實現,所以屏的賣家愿意用它寫的測試驅動。
硬件SPI的數據傳送函數就很給力了。與SPI_DMA的數據傳送比,寫入數據后自動開始傳送,而后者寫入數據與啟動傳送是兩個語句,并且硬件SPI也是外設接口,就是說它運行時有相對的獨立性,對mcu的依賴較少,所以很棒,而基于SPI_DMA的數據傳送函數,語句多了,而且還要有專門緩沖區配合,在單個數據傳送上,不如硬件SPI的傳送函數便捷。
需要說明,基于DMA的SPI接口彩屏驅動,必須使用(開啟)硬件SPI功能。這是前提條件。
在具體的應用中,傳送函數用硬件SPI的和用SPI_DAM的都可以,但既然硬件SPI的傳送函數很好用了,SPI_DMA的傳送函數寫出來更多是象征性的了。表示可行,但不表示必須用。SPI_DMA的長項在批量數據傳送上。
這里說了半天一是介紹一下情況,二是表明兩個函數的兼容性:
基于SPI_DMA數據傳送函數的程序,使用硬件SPI傳送函數都可以順暢運行
反過來
基于硬件SPI數據傳送函數的程序,在SPI_DMA傳送函數下不一定能運行
原因是緩沖區可能出問題。也可以說話程序寫的不夠好。我盡量采用SPI_DMA做依托寫函數,運行時使用硬件SPI支持。
下面是圖像顯示函數,這是SPI_DMA技術的強項。
void SPI_DMA_DISP(unsigned int x,unsigned int y,unsigned int x_width,unsigned int y_height,unsigned int total_num,unsigned int once_num,unsigned char *p,unsigned char *Q)
{
unsigned int ii,jj;
unsigned long kee;
unsigned char *kkee;
kkee=Q;//保存入口地址,方便指針復位時使用
lcd_address(x,y,x_width-1,y_height-1);//通知屏幕需要刷圖的位置,這個指令要盡量往前放,因為其中用到數據傳送,會影響傳送參數的設置。
kee=(unsigned long)(Q);//取緩沖區首地址的值,也就是打算發送給屏幕的數據源地址的值(緩沖區首地址是1:0000H。所以習慣用long類型變量)
DMA_SPI_TXAH=(kee)>>8;//把源地值高位賦給地址寄存器
DMA_SPI_TXAL=kee;//在xdata的起始地址01:0002H
DMA_SPI_AMTH=(once_num-1)>>8;
DMA_SPI_AMT=(once_num-1);
for(ii=0;ii<(total_num/once_num);ii++)//total_num/once_num必須是個整數,否則要做處理
{
for(jj=0;jj<once_num;jj++)//把要送屏的數據寫入指定的緩沖數組
{
*Q=*p;//傳送圖像數據,p指向code區的圖像數組,Q指向緩沖區buffer2
p++;
Q++;
}
Q=kkee;//傳送一輪后,緩沖區指針復位。
//把緩沖區的數據送到屏上顯示
DMA_SPI_STA=0;//清中斷標志位及錯誤標志位
DMA_SPI_CR=0xc1;//開始執行,采用主機模式,并清空FIFO, while(!(DMA_SPI_STA&0x01));//等待吧。這時可以做其它事,比如去讀一個AD值,
}
DMASPI_initial();//這是多余的指令,原想加了它可以省去數據傳送函數中的初始化
}
程序的思路是設置了兩個指針,一個指向存放圖像信息數組的頭文件*p;一個指向緩沖區存放臨時數據*Q。后者也是向屏輸出圖像信息的源地址。緩沖區必須設在xdata區。由于空間限制,要分幾次才能傳輸完總數據。所以設置了總數據量和每次數據量這兩個參數。運行時先把數據送到緩沖區,再啟動SPI_DMA功能,把數據送屏顯示。完成一組再進行第二組。直到全部傳送完。
程序的優點是啟動SPI_DMA后,mcu基本處于空閑狀態,可以去做其它事了,傳送由外設自己管理,你只要不去干涉它用到的資源就好(相關總線,特別是緩沖區)也就是說在它完成任務前,不要下達與屏顯有關的指令。下一個SPI_DMA指令,也要在前面的確定執行完后才能下達。
這個程序特點是每傳送完一輪,緩沖區指針Q都要復位一次,而p不需要,因為緩沖區是重復使用的。
這個程序能給mcu節省多少時間?我沒測量過,只是用delay();函數看了一下,要用多大的參數值,能保證SPI_DMA完成任務。就是使用延時函數替代程序中的等待查詢指令?纯葱枰嗌傺訒r能替代那個等待。
void delay(unsigned int ms)
{
int j,k;
for(j=0;j<ms;j++)
for(k=0;k<60;k++);
}
結果在delay(11111)時,圖像完全不顯示(說明傳送過程被完全打亂了),在delay(33333)時顯示基本正常了。我認為,這就是SPI_DMA節省出來的時間,而且是一輪傳送節省的時間。120x120的圖像用了五輪傳送。
接下來是字模顯示函數。
先貼一個使用普通字模數組的字模顯示函數:
void word16x32_bydma_spi(unsigned int x,unsigned int y,unsigned char segin,int font_color,int back_color,char *Q)
{
unsigned char column=0;
unsigned char tm=0,temp;
unsigned long kee;
char *point;
char *kkee;
kkee=Q;//這是保留緩沖區指針初值
point=digit_code[segin];//把要顯示的數字(segin)轉換成對應的字模地址point
//先把字模數據轉換成屏顯所用數據存到緩沖區,因為寫入緩沖區是并行處理,且沒有其它操作。所以比寫屏快很多
for(column=0;column<64;column++)//字節數循環,逐個字節進行轉換
{
temp=*point;//*point是原字模數據指針,取出原字模數值
//把字節信息換算成屏需要的數據,送到緩沖區
for(tm=0;tm<8;tm++)
{
if(temp&0x01)//注意這是低位先出模式,如果與字模不符,可以考慮改為高位先出模式
{
*Q=(font_color>>8);//Q是緩沖區指針
Q++;
*Q=(font_color);
Q++;
}
else
{
*Q=(back_color>>8);
Q++;
*Q=(back_color);
Q++;
}
temp>>=1;
}
point++;
}
//開始刷屏,因為由DMA_SPI操作,基本不需要占用mcu時間。
lcd_address(x,y,x+15,y+31);
kee=(unsigned int)(kkee);//取緩沖區首地址的值,也就是打算發送給屏幕的數據源地址的值
DMA_SPI_TXAH=(kee)>>8;//把源地值高位賦給地址寄存器
DMA_SPI_TXAL=kee;//地址是xdata的起始地址
DMA_SPI_AMTH=3;//0x03,表示的傳送總數據量是3ffh+1,也就是1024
DMA_SPI_AMT=255;//0xff
Q=kkee;//緩沖區指針復位
DMA_SPI_STA=0;//清中斷標志位及錯誤標志位
DMA_SPI_CR=0xc1;//開始執行,采用主機模式,并清空FIFO,這句明顯要跟著程序走的
while(!(DMA_SPI_STA&0x01));//等待吧。這時可以做其它事
DMASPI_initial();//
}
字模顯示函數的編寫思路是,字模數組放在code區的頭文件數組中。使用時查到需要字模,讀取后轉換成屏顯需要的數據,再存到緩沖區。然后開啟SPI_DMA刷到屏幕上。讀取和轉換程序與其它人寫的程序一樣,照抄的。區別就是轉換完后不是直接送屏顯示,而是送到了緩沖區,再由SPI_DMA統一送屏。SPI_DMA刷屏則與前面的圖像顯示一樣。這個函數的優點是不需要專門制作字模,過去的字模軟件可以直接用(臉紅的說一句,我沒用過硬件字庫)。
既然用了SPI_DMA,就會考慮能不能最大限度的發揮其作用,于是寫了一個SPI_DMA專用的字模顯示程序。由于沒有準備字模,又寫了一個專用字模數據的生成函數。生成專用字模后,放在緩沖區,就可以由專用顯示函數使用了。
void matrix_produce8x16(unsigned int font_color,unsigned int back_color)
{
char *Q;
unsigned char *point;
unsigned char column;
unsigned char temp,tm;
Q=&buffer3[0][0];//緩沖指針指向數字字模專用緩沖區buffer3
point=&number0_9_8x16[0][0];//8x16數字字模數組
for(column=0;column<160;column++)//字節數循環,逐個字節進行轉換
{
temp=*point;//*point是原字模數據指針,取出原字模數值
//把字節信息換算成屏需要的數據,送到緩沖區
for(tm=0;tm<8;tm++)
{
if(temp&0x01)//注意這是低位先出模式,如果與字模不符,可以考慮改為高位先出模式
{
*Q=(back_color>>8);//Q是緩沖區指針
Q++;
*Q=(back_color);
Q++;
}
else
{
*Q=(font_color>>8);
Q++;
*Q=(font_color);
Q++;
}
temp>>=1;
}
point++;
}
DMASPI_initial();//
}
專用字模顯示函數其實就是把普通字模全部轉換成顯示所用的字模數據,放在緩沖區專用位置,供需要時讀取使用,由于緩沖區空間有限,只能供占空間不大,需要頻繁調用的字模采用。例程中是把0-9這十個8x16的數字轉換進去了。屏幕上很小的那個字就是用它顯示的。
下面是專用字模調用程序
void word8x16_bydma_spi(unsigned int x,unsigned int y,unsigned char segin)
{
unsigned long kee;
lcd_address(x,y,x+7,y+15);
kee=(unsigned long)(&buffer3[segin][0]);//取緩沖區首地址的值,也就是打算發送給屏幕的數據源地址的值
DMA_SPI_TXAH=(kee)>>8;//把源地值高位賦給地址寄存器
DMA_SPI_TXAL=kee;//地址是xdata中對應數字的起始地址
DMA_SPI_AMTH=0;//0x00,表示的傳送總數據量是ffh+1,也就是256
DMA_SPI_AMT=255;//0xff
DMA_SPI_STA=0;//清中斷標志位及錯誤標志位
DMA_SPI_CR=0xc1;//開始執行,采用主機模式,并清空FIFO,
while(!(DMA_SPI_STA&0x01));//等待吧。這時可以做其它事, DMASPI_initial();//冗余指令
}
這程序不僅形式上簡潔多了,速度也快,特別是極少使用mcur 。
有了圖像和字符顯示,缺少的就是曲線了。橫平豎直的線太容易,不想寫了。曲線由于個人水平原因,沒找到能提高效率的思路,只好作罷。
使用SPI_DMA技術,最大的不同在于要規劃和使用緩沖區。就是xdata區的那些空間,
我為了做驗證,在例程中設了三個數組,做為緩沖區;buffer1兩個字節,作用是占位,在整個程序中沒有出現調用它的指令,占位的意思是不讓其它函數使用這個空間,留給數據傳輸函數專用,(其實這樣做是沒必要的,只是驗證實驗中期望把數據傳輸函數效率做到最高,才給它留了一個最佳緩沖地址)c251內存空間是自動安排的,不占位的話其它程序可能會來使用,使整個程序亂序。數據傳送函數使用buffer1時的語句是:
*(unsigned char *)(0x10000)=dat1;//待傳送數據送專用緩存區,就是buffer1
這樣做是因為buffer1要占住位置,只能設置在mai.c程序的模塊里,而我采用模塊化編程,數據傳送函數是在驅動模塊中的。這時不方便用數組名buffer1訪問。
第二個數組buffer2用了5600字節用來臨時存放傳送的圖像數據,其實實際使用時臨時數據是5760字節,超過了安排的緩沖區,也就是侵占了后面的空間,例程中用做開機圖像傳送時,后面的緩沖區還沒使用,所以侵占一下也沒什么。但如果后面的用上了。侵占就導致后面的數據錯誤。這也是使用指針的優點。
第三個數組是buffer3,安排了2560字節,用來放10個8x16數字的顯示數據,每個占256字節。使顯示速度達到最快。實際應用中如果有需要頻繁刷屏的字符,可以采用這種模式處理。
Buffer3定義的是一個二維數組,寫入數據時只當一維數組,按順序寫,但讀出時按二維讀。編程方便許多。
例程中常規字模顯示函數用了buffer3做緩沖區,這會影響專用字模顯示功能的使用,實際上常規字模顯示應該使用buffer2,但技術原因有不方便之處,做為實驗使用,就保留了這個失誤,這樣常規顯示和專用字模顯示只能保留一個了(因為它們的緩沖區設重復了)
在例程中有開啟高速高級SPI的語句(在mcu設置函數mcu_initial.c里)
CLKSEL &= ~0x80; //默認選擇 PLL 的 96M 作為 PLL 的輸出時鐘
USBCLK |= 0x20; //PLL 輸入時鐘 2 分頻 ,因為stc-isp設定頻率為24M
USBCLK |= 0x80; //使能 PLL 倍頻
delay(222);//等待PLL鎖頻
CLKSEL &= ~0x40; //默認 HSPWM/HSSPI 選擇主時鐘為時鐘源
HSCLKDIV = 0; //HSPWM/HSSPI 時鐘源不分頻
可以屏蔽掉,看一下對比效果。
例程驗證時使用的是STC32G12K128DIP40芯片,焊了一個小洞洞板。
FTF彩屏使用的是128x128分辨率。0.85吋,我手里大點的屏都是并口的。
調試中使用了STC-USB LINK1D硬件仿真器,個頭不大,但很給力。讓我這個第一次玩DMA的人能清楚地看到問題出在哪里,程序卡在哪里。對癥處理,節約了N多時間。
程序主要內容和編程思路已經說完了。完整的程序放在附件中可以下載查看。歡迎指導,歡迎吐槽。
分享這個程序的目的是希望使用mcu芯片的朋友們能更好的使用DMA功能,更多地發揮出它的應有效能。也為初學的朋友們提供一個實例借鑒。mcu技術的發展越來越復雜,例程對學習者的作用是不能低估的。
|
-
-
-
-
-
imagination.zip
2023-2-5 17:25 上傳
點擊文件名下載附件
下載積分: 黑幣 -5
289.36 KB, 下載次數: 78, 下載積分: 黑幣 -5
附件內為代碼
評分
-
查看全部評分
|