新聞中心

        EEPW首頁 > 嵌入式系統(tǒng) > 設計應用 > stm32實現(xiàn)printf重定向到LCD顯示屏

        stm32實現(xiàn)printf重定向到LCD顯示屏

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



        評論


        技術專區(qū)

        關閉
        主站蜘蛛池模板: 博野县| 丰城市| 五指山市| 镇江市| 鹤壁市| 荃湾区| 封丘县| 重庆市| 高碑店市| 安溪县| 大足县| 商洛市| 奎屯市| 南川市| 衡阳县| 阿尔山市| 调兵山市| 肥乡县| 瓮安县| 忻城县| 平泉县| 都匀市| 白城市| 潞西市| 定南县| 枣强县| 宁都县| 望谟县| 宁武县| 沁阳市| 广州市| 平安县| 山西省| 叙永县| 西吉县| 文化| 丽江市| 扎囊县| 沿河| 临潭县| 奇台县|