??關于STM32F4單片機,使用HAL庫自帶的SPI,驅動TFTLCD屏幕的資料網上好像不太多,正好最近我做了這項工作,把成果分享給大家。我的代碼實現了這些功能:任意坐標畫點,指定首尾坐標畫線,畫方框,指定區域顯示彩圖,顯示16* 16或者12* 12的漢字、ASCII碼,并附帶ASCII碼表與少量的漢字字庫。
硬件設計
??屏幕選擇:使用了一款低成本十六位彩屏,只要十塊錢。鏈接
??廠家看到文章請聯系我打廣告費,哈哈。
??雖然用這個屏幕的可能不多,但我了解到,只要其控制芯片是ST7735S,那么程序就應該差不多。不同的地方在于,廠家的封裝與玻璃不太一樣,玻璃有個伽馬值不同,會導致顏色看上去不太一樣。
??屏幕的引腳信息
??我的原理圖設計:使用了STM32F405RG芯片的SPI1,屏幕沒有MISO。
cubeMX中SPI的配置大致如下:
??其實SPI的速度我選的是21MBITS/s,可能再快一點也行,沒有測試。
??其它引腳比較散,都是當做IO來用,CubeMX中的配置過程就不說了,匯總如下
名稱 引腳 功能
LCD_RST PC5 屏幕復位
LCD_CD PB0 0數據1指令
SPI_MOSI PB5 數據線
SPI_CLK PB3 時鐘線
LCD_CS PB1 片選,低電平有效
LCD_LED PB2 背光,高電平有效
發送數據與指令的基本函數
??在引腳初始化以后,我定義了幾個位帶操作,方便操作引腳
#define LCD_RST PCout(5)
#define LCD_CD PBout(0)
#define LCD_CS PBout(1)
#define LCD_LED PBout(2)
??不論是發送數據還是引腳,我都采用了HAL庫提供的現成的SPI發送函數:
??很多人在使用STM32的SPI時都用模擬SPI,說STM32的硬件SPI有問題,我暫時沒有發現硬件SPI的問題。不過模擬SPI很容易講清楚原理,按位發送數據,一般寫法是這樣的:
for(i=0;i<8;i++)
{
if(dat&0x80)
{
SDA=1;
??如果你沒有使用HAL庫,可以把HAL_SPI_Transmit替換掉。
??發送數據與指令的區別就在于LCD_CD引腳的電平狀態,兩個函數如下:
/**
* @brief 向LCD屏幕寫一個字節的命令
* @param 命令內容,具體命令可以參照手冊
* @retval None
*/
static void LCD_WriteCommand(uint8_t temp)
{
LCD_CD = 0;
LCD_CS = 0;
HAL_SPI_Transmit(&hspi1,&temp, 1, 0xffff);
LCD_CS = 1;
}
/**
* @brief 向LCD屏幕寫一個字節的數據
* @param 數據
* @retval None
*/
static void LCD_WriteData(uint8_t temp)
{
LCD_CD = 1;
LCD_CS = 0;
HAL_SPI_Transmit(&hspi1,&temp, 1, 0xffff);
LCD_CS = 1;
}
??可以看出來,除了LCD_CD引腳用于切換命令,也需要操作LCD_CS來選中屏幕。個人認為操作過多操作引腳會影響效率,而發送數據的函數應用的十分頻繁,特別是對于我們選用的十六位屏幕,每個像素都需要十六位的數據,所以,我們經常用到的功能是發送個十六位的數據。代碼可以這么寫,調用兩次發送8位數據的函數:
static void LCD_WD_U16(u16 temp)
{
LCD_WriteData(temp>>8);
LCD_WriteData(temp);
}
??由于要操作兩次IO,所以我稍微做了一點優化:
/**
* @brief 向LCD屏幕寫兩個字節的數據
* @param 16位的數據
* @note 此函數可以直接調用LCD_WriteData兩次,但是IO的操作是多余的
* 由于每個圖片的數據都是16位的,所以此函數很常用,因此稍作優化,減少操作IO
* @retval None
*/
static void LCD_WD_U16(u16 temp)
{
u8 tempBuf[2];
tempBuf[0] = temp>>8;
tempBuf[1] = temp;
LCD_CD = 1;
LCD_CS = 0;
HAL_SPI_Transmit(&hspi1,tempBuf, 2, 0xffff);
LCD_CS = 1;
}
??同理寫了一個函數,用于發送數組。彩圖數組動輒都是上萬位的,并且是連續發送數據,所以也不需要操作多次IO。
/**
* @brief 向LCD屏幕寫一個數組的長度
* @param 數組地址與長度
* @note 此函數可以直接調用LCD_WriteData若干次,但是IO的操作是多余的
* 由于每個圖片的數據都是16位的很長的數組,所以此函數很常用,因此稍作優化,減少操作IO,一個圖片的數組值操作一次IO
* @retval None
*/
static void LCD_WD_buf(uint8_t *pData, uint16_t Size)
{
LCD_CD = 1;
LCD_CS = 0;
HAL_SPI_Transmit(&hspi1,pData, Size, 0xffff);
LCD_CS = 1;
}
初始化與定位
??初始化代碼太長,就不放了。其實初始化代碼是廠家提供的,只不過原來是51程序,我移植了下。
??屏幕的顯示需要坐標系,定位操作其實就是發個特定的命令,表示設置x/y軸,在發送特定的數據,表示具體位置。操作思路在《ST7735S手冊》中都有體現,例如設置列地址:
??我們找到了設置列地址的命令,再把自己需要的坐標計算出來,假如全屏顯示:
/**
* @brief 設置顯示區域為全屏
* @param None
* @retval None
*/
static void Full_Screen(void)
{
LCD_WriteCommand(0x2A); //設置列地址
LCD_WriteData(0x00);
LCD_WriteData(0x02);
LCD_WriteData(0x00);
LCD_WriteData(0x81);
LCD_WriteCommand(0x2B); //設置行地址
LCD_WriteData(0x00);
LCD_WriteData(0x03);
LCD_WriteData(0x00);
LCD_WriteData(0x82);
LCD_WriteCommand(0x2C); //寫內存
}
??設置某個點的坐標:
/**
* @brief 設置某個點的坐標
* @param 點的橫縱坐標
* @note 坐標的起點為(2,3)
* @retval None
*/
static void LCD_SetXY(u16 x,u16 y)
{
LCD_WriteCommand(0x2A); //設置橫軸
LCD_WD_U16(x+2);
LCD_WriteCommand(0x2B); //設置縱軸
LCD_WD_U16(y+3);
LCD_WriteCommand(0x2C); //寫內存
}
??設置某個區域的坐標:
/**
* @brief 設置某個顯示區域的坐標
* @param 區域左上角的坐標與右下角的坐標
* @note 坐標的起點為(2,3)
* @retval None
*/
static void LCD_SetArea(u16 x0, u16 y0,u16 x1, u16 y1)
{
LCD_WriteCommand(0x2A); //設置橫軸
LCD_WD_U16(x0+2);
LCD_WD_U16(x1+2);
LCD_WriteCommand(0x2B); //設置縱軸
LCD_WD_U16(y0+3);
LCD_WD_U16(y1+3);
LCD_WriteCommand(0x2C); //寫內存
}
顏色的確定
??所謂十六位真彩色,意思就是每個像素的顏色由十六位決定。我們在初始化函數中設置的是這樣分配的:
??紅色5位,綠色6位,藍色5位
??很容易想到白色的RGB值就是0xffff,黑色是0x0000。其它還有幾個顏色的定義如下:
#define RED 0xf800
#define GREEN 0x07e0
#define BLUE 0x001f
#define YELLOW 0xffe0
#define WHITE 0xffff
#define BLACK 0x0000
#define PURPLE 0xf81f
??一定要注意,高位在前。有很多取色工具可以幫我們算出某個顏色的RGB值。
畫點、線、框
??前邊已經寫了確定點坐標的方法,畫點就十分簡單了:
/**
* @brief 畫一個點
* @param 點的橫縱坐標,點的顏色
* @retval None
*/
void LCD_DrawPoint(u16 x,u16 y,u16 color)
{
LCD_SetXY(x,y);
LCD_WD_U16(color);
}
??畫線函數理論上來講就是調用多次畫點的函數。如果是橫平豎直的線,那十分簡單了。如果是斜線呢?那就需要考慮斜率了。由于像素是離散的,所以線上的點,我們只處理所謂的整數部分,代碼比較復雜,主要是因為整型變量處理四舍五入的小數部分稍微有點吃力。
/**
* @brief 畫一條線
* @param 線的起點與終點的橫縱坐標,顏色
* @note 可以畫斜線
* @retval None
*/
void LCD_DrawLine(u16 x0, u16 y0,u16 x1, u16 y1,u16 Color)
{
int dx, // x軸上的距離
dy, // y軸上的距離
dx2, // 計算坐標的臨時變量
dy2,
x_inc, // inc表示點的“生長方向” x_inc>1代表從左向右
y_inc, // inc表示點的“生長方向” x_inc>1代表從上向下(左上角是坐標原點)
error, // 由于坐標點只有整數,是離散的不是連續的,需要變量用于四舍五入的計算
index;
LCD_SetXY(x0,y0);
dx = x1-x0;//計算x距離
dy = y1-y0;//計算y距離
if (dx>=0)
{
x_inc = 1;
}
else
{
x_inc = -1;
dx = -dx;
}
if (dy>=0)
{
y_inc = 1;
}
else
{
y_inc = -1;
dy = -dy;
}
dx2 = dx << 1; //相當于乘以2,如此一來,四舍五入的誤差就變成了不到1舍,大于1入
dy2 = dy << 1;
if (dx > dy)//x距離大于y距離,那么對于每個x軸上只有一個點,每個y軸可能只有半個點
{
error = dy2 - dx;
for (index=0; index <= dx; index++)//要畫的點數不會超過x距離
{
LCD_DrawPoint(x0,y0,Color);
if (error >= 0) //如果error>0 說明真實的y的誤差>0.5了,實際上應該+1了
{
error-=dx2;
y0+=y_inc;//增加y坐標值
}
error+=dy2;
x0+=x_inc;//x坐標值每次畫點后都遞增1
}
}
else
{
error = dx2 - dy;
for (index=0; index <= dy; index++)
{
LCD_DrawPoint(x0,y0,Color);
if (error >= 0)
{
error-=dy2;
x0+=x_inc;
}
error+=dx2;
y0+=y_inc;
}
}
}
??由于界面中,我們常常需要劃一個方框,或者稱之為“按鈕”,所以我又封裝了一個函數:
/**
* @brief 畫一個方框,或者稱之為按鈕
* @param 方框左上角和右下角的點的坐標,顏色
* @note 右邊和下邊的線自帶加粗效果 如需花在屏幕最邊緣無加粗效果
* @retval None
*/
void LCD_DrawBTN(u16 x1,u16 y1,u16 x2,u16 y2,u16 Color)
{
LCD_DrawLine(x1, y1, x2,y1, Color); //H
LCD_DrawLine(x1, y1, x1,y2, Color); //V
LCD_DrawLine(x1+1,y2-1,x2,y2-1, Color); //H 加粗 多畫一條線
LCD_DrawLine(x1, y2, x2,y2, Color); //H
LCD_DrawLine(x2-1,y1+1,x2-1,y2, Color); //V
LCD_DrawLine(x2 ,y1 ,x2,y2, Color); //V
}
顯示純色背景與圖片
??我們已經做到了全屏顯示,那么純色背景的顯示就很簡單了:
/**
* @brief 全屏顯示純色圖片,可用作清屏
* @param 顏色
* @retval None
*/
void LCD_BG_Color(u16 color)
{
int i=16384;//128*128
Full_Screen();
while(i-->0)//不可使用無符號數據類型
LCD_WD_U16(color);
}
??一共有16384個像素,那就調用16384次畫點的程序。只不過這里我稍作了一點優化,不用再管點的坐標了,因為已經設置了顯示區域是全屏。
??我專門寫了一個函數,顯示各個純色圖片,可以看出有沒有那個像素點顏色顯示的不全:
/**
* @brief LCD屏幕測試,依次顯示純色照片,可以看出屏幕的每一個像素點是否正常
* @param 每種顏色持續的時間,單位ms
* @retval None
*/
void LCD_Test(u16 delay_Time)
{
LCD_BG_Color(BLACK);
HAL_Delay(delay_Time);
LCD_BG_Color(RED);
HAL_Delay(delay_Time);
LCD_BG_Color(GREEN);
HAL_Delay(delay_Time);
LCD_BG_Color(BLUE);
HAL_Delay(delay_Time);
LCD_BG_Color(WHITE);
HAL_Delay(delay_Time);
}
??顯示全屏幕的背景圖片跟純色背景思路類似,把數據的來源改為數組就好:
/**
* @brief 在LCD屏幕中顯示一個全屏的背景圖片
* @param 無
* @note 只適用于128*128的屏幕,為了效率把長度直接計算了出來
* 由于圖片數組要放在ROM,所以用了const,因此要做強制類型轉化(u8 *)
* 如果是雙工,則收發需要同一個數組,數組不能放在ROM
* @retval None
*/
void LCD_BG_Image(void)
{
uint32_t i=32768; //Z144_HEIGHT*Z144_WIDTH*2;
Full_Screen();
LCD_WD_buf((u8 *)LCD_MOTOR,i);
}
??接下來就是指定區域顯示圖片了,函數也很簡單:
/**
* @brief 按照指定的坐標顯示圖片
* @param 圖片左上角的坐標與右下角的坐標,圖片地址
* @note 圖片大小與坐標必須對應
* 由于圖片數組要放在ROM,所以用了const,因此要做強制類型轉化(u8 *)
* 坐標范圍[0,127]
* @retval None
*/
void LCD_Show_Image(u16 x0, u16 y0,u16 x1,u16 y1,const unsigned char *p)
{
int i = (x1-x0)*(y1-y0)*2;
LCD_SetArea(x0,y0,x1,y1);
LCD_WD_buf((u8 *)p,i);
}
??那么圖片數組從哪里來?
??取模得到。我去網上隨便找了一張彩圖,然后把尺寸編輯為100* 100像素。取模軟件如下設置:
??然后可以得到圖片轉化為的數組。
顯示文字與字符
??中文字符都采用GBK(或者說GB2312)編碼,英文字符采用ASCII碼。顯示文字其實與顯示圖片的原理是一樣一樣的,思路在我另一篇博客中介紹過。
??先說漢字的取模設置:
??我使用了結構體儲存漢字,結構體的每個元素都包含文字(或者稱之為索引)和它對應的編碼。因此得到的數組要稍加修改,增加雙引號與漢字,例如北京:
//修改前
0x04,0x40,0x04,0x40,0x04,0x40,0x04,0x44,0x04,0x48,0x7C,0x50,0x04,0x60,0x04,0x40,
0x04,0x40,0x04,0x40,0x04,0x40,0x04,0x42,0x1C,0x42,0xE4,0x42,0x44,0x3E,0x04,0x00,/*"北",4*/
0x02,0x00,0x01,0x00,0xFF,0xFE,0x00,0x00,0x00,0x00,0x1F,0xF0,0x10,0x10,0x10,0x10,
0x10,0x10,0x1F,0xF0,0x01,0x00,0x11,0x10,0x11,0x08,0x21,0x04,0x45,0x04,0x02,0x00,/*"京",5*/
//修改后
"北",0x04,0x40,0x04,0x40,0x04,0x40,0x04,0x44,0x04,0x48,0x7C,0x50,0x04,0x60,0x04,0x40,
0x04,0x40,0x04,0x40,0x04,0x40,0x04,0x42,0x1C,0x42,0xE4,0x42,0x44,0x3E,0x04,0x00,/*"北",4*/
"京",0x02,0x00,0x01,0x00,0xFF,0xFE,0x00,0x00,0x00,0x00,0x1F,0xF0,0x10,0x10,0x10,0x10,
0x10,0x10,0x1F,0xF0,0x01,0x00,0x11,0x10,0x11,0x08,0x21,0x04,0x45,0x04,0x02,0x00,/*"京",5*/
??顯示的字符要先判斷是中文的還是英文的。英文字符的值小于128,顯示函數如下:
/**
* @brief 輸出16*16的漢字或8*16的字符,函數可以自動識別是中文字符還是ASCII
* @param 第一個字符的坐標,漢字顏色,背景顏色,需要顯示的字符串。背景顏色為0表示不畫背景
* @retval None
*/
void LCD_DrawFont_GBK16(u16 x, u16 y, u16 fc, u16 bc, u8 *s)
{
unsigned char i,j;
unsigned short k,x0;
x0=x;
while(*s)
{
if((*s) < 128) //如果是ASCII碼,那么編號小于128
{
k=*s;
if (k==13) //回車
{
x=x0;//記錄本行第一個字符的位置
y+=16;
}
else
{
if (k>32) k-=32; else k=0; //ASCII前32個都不是字符,除了回車,對顯示沒有影響,所以字庫不儲存
for(i=0;i<16;i++)
{
for(j=0;j<8;j++)
{
if(ASC16[k*16+i]&(0x80>>j)) //如有數據,畫字體 從高位到低位取出字符
上一篇:STM32自帶GB2312字庫顯示漢字
下一篇:STM32掌機教程9,完成掌機
推薦閱讀
史海拾趣
Control Sciences Inc公司在電子行業的初期,就以其技術創新而聞名。公司團隊不斷研發新的控制技術,成功打破了當時行業的局限。他們推出的首款智能控制系統,不僅提高了生產效率,還大大降低了能源消耗,為電子行業帶來了巨大的經濟效益。這一創新成果使得Control Sciences Inc在業界嶄露頭角,贏得了眾多客戶的青睞。
為了確保產品質量的穩定和可靠,Control Sciences Inc建立了一套完善的質量管理體系。公司從原材料采購、生產過程到產品出廠,都實行了嚴格的質量控制。同時,公司還引入了先進的質量檢測設備和方法,確保每一臺產品都符合客戶的要求。這一舉措使得Control Sciences Inc的產品在市場上贏得了良好的聲譽。
作為一家有社會責任感的企業,Control Sciences Inc始終關注環境保護和社會公益。公司積極推廣節能減排技術,減少生產過程中的環境污染。此外,公司還積極參與社會公益活動,為社會的發展貢獻自己的力量。這些舉措不僅提升了公司的社會形象,也贏得了社會各界的廣泛認可。
以上只是基于通用框架編寫的示例故事,具體的內容需要您根據Control Sciences Inc公司的實際情況進行調整和補充。在編寫過程中,請確保遵循事實,避免加入主觀評價或猜測。
在電子行業中,振動控制是一個至關重要的領域。Cedrat Technologies憑借其在壓電技術領域的深厚積累,成功開發了一套主動振動控制系統。該系統通過精確監測機械結構或設備的振動情況,利用壓電執行器產生相位相反的振動信號,有效抵消原始振動。這一技術的問世,不僅大幅提升了設備的穩定性和精度,也為機械結構的振動控制開辟了新的途徑。Cedrat Technologies因此在電子行業中樹立了新的技術標桿。
在光電技術不斷發展的背景下,Advanced Photonix開始研發太赫茲傳感器產品。這種傳感器產品主要針對無損檢測和質量控制市場,如行李和貨物的安全檢查等。經過長時間的努力,公司成功開發出了一系列高性能的太赫茲傳感器,這些產品憑借其高可靠性和精確性,迅速贏得了市場的青睞。通過與各大航空公司和物流企業的合作,Advanced Photonix的太赫茲傳感器產品在全球范圍內得到了廣泛應用,為公司的快速發展注入了新的動力。
在電子行業的早期,Advanced Photonix以其卓越的光學技術脫穎而出。公司創始團隊由一群富有遠見的科學家和工程師組成,他們專注于研發高速光電子和高性能的光纖測試產品。經過無數次的實驗和試錯,團隊終于成功開發出一種具有創新性的光纖傳感技術,這一技術為電信行業提供了前所未有的分布式光纖傳感解決方案。這一技術突破不僅為Advanced Photonix贏得了市場聲譽,也奠定了公司在電子行業的重要地位。
要求是在傳送帶的2端分別放置光電傳感器,中間用4個“7劃數碼管”(就是電子顯示的那個8)顯示傳送帶的速度 需要自行安裝排布所有的電路并分析 我們已完成光電傳感器的電路 問題在于如何使光電傳感器的輸出電壓作為測量速度的開始和結束?(需自 ...… 查看全部問答∨ |
|
我最近在做一塊ARM板上的藍牙底層初始化驅動,環境如下 板子:ARM11板 Linux內核版本:2.6.18 藍牙模塊:采用CSR的Bluecore5核心,和CPU通過UART相連 藍牙驅動:1.自己根據藍牙的datasheet在系統啟動時給藍牙模塊發送了一條Reset信號,并初始化 ...… 查看全部問答∨ |
求購windows CE下智能輸入法,最好是T9輸入法,至少含拼音和筆畫,windows CE操作系統,鍵盤4X5。有意聯系xiao615@126.com.… 查看全部問答∨ |
我想打開png圖片,上網查到要用SHLoadImageFile函數可以,但是使用SHLoadImageFile函數需要aygshell.lib,我在網上找不到aygshell.lib,卻下載了AYGSHELL.DLL,可是光有AYGSHELL.DLL卻不知道應該怎么用它。 希望高人指點一下怎么用AYGSHELL.DLL來 ...… 查看全部問答∨ |
早晨起來,一看上海天馬上賽車場已經裝扮一新,賽道內有專門的維修通道,與正式的F1比賽管理別無二致,已悄然感受到大戰的氣息。在賽事現場,熱辣的美女、轟鳴的馬達、絢麗的奧鈴CTX賽車、激情吶喊的觀眾,這就是2010(第二屆)福田奧鈴CTX中國勒芒 ...… 查看全部問答∨ |
在做AD采樣時,初始化AD時,如果沒有這段代碼,發現采樣結果不對,加上后,檢測結果正常: AdcRegs.ADCTRL1.bit.RESET=1; //復位整個ADC模塊 DSP28x_usDelay(1); ...… 查看全部問答∨ |
數字信號處理(Digital Signal Processing,簡稱PCB設計DSP)是一門涉及許多學科而又廣泛應用于許多領域的新興學科。 介紹PCB設計DSP的歷史以及一些基礎參數,目前,TI公司是最大的PCB設計DSP生產商. 20世紀60年代以來,隨著計算機 ...… 查看全部問答∨ |