stm32實(shí)現printf重定向到LCD顯示屏
http://blog.sina.com.cn/s/blog_6e22f4ce01010uud.html
int fputc(int ch, FILE* f)
{
}
看起來(lái)很熟悉吧。但是我們現在是要把它重定向到LCD,怎么實(shí)現呢?
同樣的方法,把傳遞進(jìn)來(lái)的參數轉發(fā)到LCD就行了!嘿嘿,首先要有LCD驅動(dòng),也就是有LCD打印一個(gè)字符的函數(中文是兩個(gè)字節),舉一個(gè)例子。假設已經(jīng)有一個(gè)打印一個(gè)字符的函數
LCD_Ascii_one();
函數參數先不理,來(lái)到這一步就已經(jīng)成功了一半了。像串口一樣,只不過(guò)把
USART_SendData(USART1, (uint8_t)ch);
修改成
LCD_Ascii_One(...);//其中參數...中包含ch信息
這樣, 整個(gè)框架就形成了, 下面是具體實(shí)現過(guò)程
先說(shuō)明一下我的LCD_Ascii_One函數:
LAC_Ascii_One(uint8_t X, uint16_t Y, uint8_t *Chinese, uint16_t Color);
四個(gè)參數的含義分別為:
uint8_t X 打印字符的橫坐標
uint16_t Y 打印字符的縱坐標
uint8_t *Chinese 打印字符的字模首地址 //字模提取軟件得到的, 字模放在sdcard, 使用時(shí)需要打開(kāi)文件
uint16_t Color 打印字符的顏色
獲取字符的字模不是這里的重點(diǎn), 同學(xué)們另找資料.
string = Get_Ascii(a); //獲取字符a的字模
調用LCD_Ascii_One(0, 10, string, WHITE);//打印
就實(shí)現了在LCD上的位置(0, 10)打印字符a, 顏色為白色
所以fputc函數里面會(huì )有這么一句
LCD_Ascii_One(x, y, ch, color);
不過(guò)還是不夠, 我們需要打印一個(gè)字符后跳到下一個(gè)位置, 不然下次打印就覆蓋了本次字符了
所以還必須封裝x, y, 讓他們帶有記憶功能, 呵呵,關(guān)鍵字static派上用場(chǎng)了
fputc函數需要聲明
static uint16_t x = 0, y = 0;
為什么是uint16_t 應為我的LCD顯示屏是240 * 320的
uint8_t 無(wú)法表示320, 所以是uint16_t, x也就順便uint16_t了哈
調用玩LCD_Ascii_one之后
x += 8; //因為我提取字模是8 * 16的Ascii字模
還有一部是fputc需要的
return ch;
這樣功能就完善一點(diǎn)了,加把勁, 繼續換行功能
在調用LCD_Ascii_One()之前, 先判斷x是否越界, 也就是當前這一行已經(jīng)無(wú)法打印一個(gè)字符了
if(x > X_MAX - 8) //X_MAX是宏定義, 該值為240
{
}
但是y也可能越界, 也就是說(shuō)已經(jīng)到了LCD的底部了, 我這里處理是越界直接退出, 不打印字符了
if(y > Y_MAX - 16) //Y_MAX是宏定義, 該值是320
{
}
換行功能還有調用printf()時(shí)候的n
所以還是老樣子, 跟串口一樣:
if(ch == n)
{
}
是否越界是上面的函數在判斷.
來(lái)到這里, 我們重新再這里一下fputc的內容
需要打印一個(gè)字符自動(dòng)跳到下一個(gè)位置, x越界換行, y越界退出
當然這里只是一個(gè)框架, 還有其他需要封裝的, 例如中文, Ascii區分, 接下來(lái)會(huì )討論
只是fputc至少必須包含語(yǔ)句:
int fputc(int ch, FILE* f)
{
}
(能夠理解的同學(xué)接下來(lái)可以測試自己封裝一下中英文了, 接下來(lái)我們繼續討論實(shí)現中英文混輸.)
嘿嘿, 我的大概框架就是這樣, 當然這樣的功能還是不夠強大的, 對, 我們需要的是再強大一點(diǎn)的printf, 接下來(lái)是, 實(shí)現中英文混輸, 可變參數C語(yǔ)言已經(jīng)幫我們實(shí)現了, 也就是說(shuō)我們調用printf("hello %s", str);已經(jīng)能打印出hello world并且換行了, 但是如果printf("你好n");會(huì )怎么樣? 亂碼? 沒(méi)錯, 我們現在只是實(shí)現了Ascii打印, 并沒(méi)有中文, 這是與串口所不同的, 因為串口打印到電腦的超級終端, 超級終端已經(jīng)幫我們實(shí)現了這個(gè)功能了, 所以printf重定向到LCD還有再封裝, 對, 要更強大的函數, 實(shí)現完美的打印功能!!
中英文混輸先要有一點(diǎn)預備知識.
1. 一個(gè)中文占兩個(gè)字節, 一個(gè)Ascii占一個(gè)字節.
2. Ascii最高字節為0, 中文的兩個(gè)字節最高字節都是1
上面的第二點(diǎn)再說(shuō)明一下
如果將Ascii看成是無(wú)符號數字, 它的數值小于128, 也就是0~127
如果將中文的兩個(gè)字節分別看成無(wú)符號數字, 第一個(gè)字節范圍為128~255, 第二個(gè)字節也是128~255
嘿嘿, 判斷中英文就有辦法了~~~~
if(ch & 0x80) //判斷最高位是否為1, 最高位為1表達式ch & 0x80為真
{
}
else
{
}
中文有兩個(gè)字節, 所以要進(jìn)入fputc兩個(gè)才能打印出一個(gè)中文, 所以還必須把先進(jìn)來(lái)的ch保存起來(lái), 等下一個(gè)字節進(jìn)來(lái)在打印, 當然Ascii如果進(jìn)來(lái)就直接打印. 這樣我們又要有記憶功能的tmp[2]分別保存中文的兩個(gè)字節了. 并且需要記錄是計入的是第一個(gè)中文字節還是第二個(gè)中文字節
static uint8_t flag;
static uint8_t tmp[2];
當然, 要打印中文, 還必須有一個(gè)打印中文的函數, 我這里的函數是
LCD_Chinese_One(uint8_t X, uint16_t Y, uint8_t *Chinese, uint16_t Color);
uint8_t X 打印中文的橫坐標
uint16_t Y 打印中文的縱坐標
uint8_t *Chinese 打印中文的字模
uint16_t Color 打印中文的顏色
string = Get_Chinese("好"); //獲取好的字模
例如調用LCD_Chinese_One(5, 20, string, WHITE); //打印
就在LCD的位置(5, 20)打?。⒑茫?, 顏色為白色
來(lái)到這里, 相信同學(xué)們都懂了吧, 啰嗦一下, 我們再來(lái)看看fputc應該怎么寫(xiě)
int fputc(int ch, FILE* f)
{
}
至此, 函數封裝完成了.
測試調用一下
uint8_t str = "hello world";
printf("你好, 這是一個(gè)測試程序n%sn", str);
在LCD上打印的是
你好, 這是一個(gè)測試程序
hello world
嘿嘿, 重定向成功了!!!
相信已經(jīng)能滿(mǎn)足廣大開(kāi)發(fā)者的需求了, 有待完善的地方是控制符tr等, 不過(guò)都是相當簡(jiǎn)單的.
下面給出我的封裝, 不過(guò)我是把字模放到sdcard的, 所以需要打開(kāi)文件, 讀取文件等操作, 但是原理還是一樣的, 嘿嘿~~~~~~
#ifdef STDOUT_LCD
int fputc(int ch, FILE* f)
{
static uint32_t Offset, Read_Count;
static uint8_t Read_Font[32];
static FRESULT File_Status;
static FIL File;
static uint16_t x = 0, y = 0;
static uint8_t flag = 0;
static uint8_t tmp[2];
File_Status = f_open(&File, CHINESE_FONT, FA_OPEN_EXISTING | FA_READ); //中文字庫
if(ch == n) //換行處理
{
x = 0; //...
y += 18; //...
return ch;
}
if(ch & 0x80) //中文處理
{
if(flag == 0) //中文的兩個(gè)字節判斷, flag == 0 為第一個(gè)字節
{
tmp[0] = (uint8_t)(ch); //把第一個(gè)字節保存進(jìn)來(lái)
flag = 1; //接下來(lái)是第二個(gè)字節
return ch; //按照C語(yǔ)言官方庫, 返回ch
}
else //是中文的第二個(gè)字節
{
tmp[1] = (uint8_t)(ch); //保存該字節
flag = 0; //下一次是第一個(gè)字節
if(x > X_MAX - 16) //判斷是否需要換行
{
x = 0; //換行操作...
y += 18; //...
}
if(y > Y_MAX - 16) //判斷是否越界
{
return ch; //越界退出
}
File_Status = f_open(&File, CHINESE_FONT, FA_OPEN_EXISTING | FA_READ);
if(Check_FileStatus(File_Status)) //判斷文件操作狀態(tài)
{
Offset = Chinese_Offset(tmp); //根據中文字庫字模提取軟件計算出漢字偏移位置
f_lseek(&File, Offset); //文件指針移至偏移位置
}
if(Check_FileStatus(File_Status)) //判斷文件操作狀態(tài)
{
File_Status = f_read(&File, Read_Font, 32, &Read_Count);
}
if(Check_FileStatus(File_Status)) //判斷文件操作狀態(tài)
{
LCD_Chinese_One(x, y, Read_Font, WHITE);//調用中文打印函數打印漢字
x += 16; //液晶橫坐標+16, 中文為16*16的
}
File_Status = f_close(&File); //關(guān)閉文件
}
}
else
{
if(x > X_MAX - 8) //判斷是否需要換行
{
x = 0; //換行操作...
y += 18; //...
}
if(y > Y_MAX - 16) //判斷是否越界
{
return ch; //越界退出
}
File_Status = f_open(&File, ASCII_FONT, FA_OPEN_EXISTING | FA_READ);
if(Check_FileStatus(File_Status)) //判斷文件操作狀態(tài)
{
Offset = ((uint8_t)(ch) - 0x20) << 4; //計算出Ascii偏移地址, 要先跳過(guò)Ascii控制字符
f_lseek(&File, Offset); //文件指針移至偏移位置
}
if(Check_FileStatus(File_Status))//判斷文件操作狀態(tài)
{
f_read(&File, Read_Font, 16, &Read_Count);//讀取字模信息
}
if(Check_FileStatus(File_Status))//判斷文件操作狀態(tài)
{
LCD_Ascii_One(x, y, Read_Font, WHITE); //調用Ascii打印函數打印字符
x += 8;//Ascii像素為8*16, 橫坐標+8
}
File_Status = f_close(&File); //關(guān)閉文件
}
return ch;
}
#endif //#ifdef STDOUT_LCD
評論