Sipeed Longan NanoでFONTX2形式のフォントを利用する
12月半ばに腕を骨折してしまい、更新が滞っておりました。ようやくリハビリも進み、キーボードを打つのに支障がなくなってきたので、またぼちぼち書いていきたいと思います。本日は、先日に引き続き、Longan Nanoのネタです。
Longan NanoのLCD制御ライブラリを眺めていて、比較的簡単にオリジナルフォントの表示を行うことができそうに思えましたので、さっそく改造してみました。フォントデータとして、扱いやすく入手の容易なFONTX2形式に対応し、半角文字だけでなく漢字を含む全角文字の表示にも対応してみました。FONTX2のデータはサイズが大きいので、マイコンのフラッシュメモリではなくmicroSDカードに置きたいところですが、そのためにはFATファイルシステムへの対応が必要となります。幸いにもSipeed社のサンプルにはmicroSDカードから画像データを読み込んで表示するためにChaN様作のFatFsが組み込まれており、今回はこれを流用しました。ただ、Sipeed社の開発した低レベルI/Oのコード(tf_card.c 内の rcvr_spi_multi 関数)にバグがあり、そのままだとバッファオーバーランを起こして漢字フォントの表示に失敗するため、修正を行っています。最初はこの不具合に気づかず、自分の書いたコードを何度も見直して何日も無駄にしてしまいました…。今回は、日本語の文字列に対応するためUTF-8を利用できるようにしましましたが、みいちゃん様によるUTF8→UTF16の変換アルゴリズムと、FatFsに含まれるUTF16→JISへの変換関数(ffunicode.c)を改変して利用させていただきました。また、FONTX2データのアドレス計算を行う部分も、ChaN様のサイトで公開されているものを利用させていただきました。PlatformIO(GigaDevice GD32V SDK)用のライブラリとサンプルプログラムのソースコードはGitHubにて公開しています。
以下に、利用方法を書いておきます。
1. microSDカードにFONTX2のフォントデータを保存します。
2. include/lcd/lcd.h を編集し設定を行います。FONTX2関連(+α)の設定は以下の部分で行います。
USE_FONTX2 でFONTX2を使用するかどうかを指定します。設定値は以下のとおりです。
0:FONTX2を使用せず、oledfont.h で定義されたフォントのみを使用します
1:2バイト文字のみFONTX2を使用し、1バイト文字はoledfont.h で定義されたフォントを使用します
2:1バイト文字、2バイト文字ともFONTX2を使用します
FONTX2を使用する場合には、1バイト文字フォントのファイル名を LCD_AFONT で、2バイト文字フォントのファイル名を LCD_KFONT で指定します。
LCD_ASPACE, LCD_KSPACE で、FONTX2フォントで文字列表示を行う場合の水平方向の文字間隔(右側のスペーシング)をドット数で指定できます。また、FONT_HEIGHT で、文字列表示の際の自動改行時の1行の高さを指定できます。
USE_UTF8STR を定義すると文字列表示を行う際に日本語の文字列(UTF-8)を使用できますが、その場合にはUnicodeからShift JISへの変換テーブルが組み込まれるためコードサイズが大きくなります。
FONTX2_USELED を定義するとオンボードLEDでステータス表示を行います。フォントデータの読み込みエラー時に赤、SDカードへのアクセス中は緑に点灯します(主にデバッグ用です)。
LCD_COMPAT は、Sipeed社提供のオリジナルライブラリと互換性のある関数名を使用する場合に定義します。というのも、オリジナルのライブラリのほとんどの関数は“LCD_”から始まるのですが、LCD初期化関数 Lcd_Init と、円を描画する Draw_Circle だけが例外となっており統一感がなかったため、今回作成したものではそれぞれ LCD_Init, LCD_DrawCircle に改名しました。ただ、そのままだとこれまで開発したプログラムが動作しなくなるため、この定数を定義することによって旧関数名でも利用できるようにしています。
3. include/lcd/fontx2.h で扱うフォントの1文字当たりのデータサイズを指定します。
4. サンプルの main.c と同様に、lcd.h をインクルードします。
以上の手順でOKです。以下に簡単に関数のリファレンスを記しておきます。
lcd.c グローバル変数:
unsinged int BACK_COLOR; :文字表示の際の背景色
unsigned char image[12800]; :LCD_ShowPicture() で表示するイメージを格納する配列
unsigned char ankfont_width; :半角文字の横方向ドット数
lcd.c 関数 (低レベルI/O関数は除く):
void LCD_Init(void); ( = Lcd_Init )
LCDを初期化する。
void LCD_Clear(u16 Color);
LCDをクリアする(=Colorで指定した色で全面を塗りつぶす)。
Color = 背景色
void LCD_ShowChinese(u16 x,u16 y,u8 index,u8 size,u16 color);
漢字を表示する(別途 oeldfont.h の中でパターン定義されているもののみ)。
x, y = 座標 index = 漢字のインデックス番号(定義されている順に0~)
size = フォントサイズ(16/32) color = 描画色
void LCD_DrawPoint(u16 x,u16 y,u16 color);
点を打つ。
x, y = 座標 color = 描画色
void LCD_DrawPoint_big(u16 x,u16 y,u16 color);
大きな点を打つ(実際には3x3ピクセルの正方形を描く)。
x, y = 座標 color = 描画色
void LCD_Fill(u16 xsta,u16 ysta,u16 xend,u16 yend,u16 color);
矩形領域を塗りつぶす。
xsta, ysta = 左上座標 xend, yend = 右下座標 color = 描画色
void LCD_DrawLine(u16 x1,u16 y1,u16 x2,u16 y2,u16 color);
直線を描画する。
x1, y1 = 起点座標 x2, y2 = 終点座標 color = 描画色
void LCD_DrawRectangle(u16 x1, u16 y1, u16 x2, u16 y2,u16 color);
矩形を描画する。
x1, y1 = 起点座標 x2, y2 = 終点座標 color = 描画色
void LCD_DrawCircle(u16 x0,u16 y0,u8 r,u16 color); ( = Draw_Circle )
円を描画する。
x0, y0 = 中心 r = 半径 color = 描画色
void LCD_ShowChar(u16 x,u16 y,u16 num,u8 mode,u16 color);
文字を表示する
x, y = 座標 num = 文字コード(ASCII/SJIS) mode = モード color = 描画色
※2バイト文字の場合はShift-JISコードを指定してください
※mode = 1 のとき OR、 mode = 0 のとき 上書き(文字背景を BACK_COLOR で塗りつぶした上に描画)
void LCD_ShowString(u16 x,u16 y,const u8 *p,u16 color);
文字列を表示する(必ず上書きモードとなる)。
x, y = 座標 p = ASCII/UTF8文字列へのポインタ color = 描画色
void LCD_ShowNum(u16 x,u16 y,u16 num,u8 len,u16 color);
整数値を表示する。
x, y = 座標 num = 数値 len = 表示文字数 color = 描画色
void LCD_ShowNum1(u16 x,u16 y,float num,u8 len,u16 color);
小数値を表示する。
x, y = 座標 num = 数値 len = 表示文字数 color = 描画色
void LCD_ShowPicture(u16 x1,u16 y1,u16 x2,u16 y2);
イメージを表示する( image[] に格納されたデータを表示する)。
x1, y1 = 左上座標 x2, y2 = 右下座標
void LCD_ShowLogo(void);
ロゴマークを表示する( bmp.h で定義されたデータを表示する)。
void LCD_ShowHex(u16 x,u16 y,u16 num,u8 len,u16 color)
16進数を表示する
x, y = 座標 num = 数値 len = 表示文字数 color = 描画色
fontx2.c グローバル変数:
FH font[FONTX2_FONTNUM];
FONTX2ヘッダ読み込みバッファ
(FH は、FONTX2ヘッダ構造体)
uint8_t fontdata[FONTX2_FONTSIZE];
フォントパターン格納バッファ
fontx2.c 関数:
void fontx2_init( void );
FONTX2ライブラリを初期化する。
uint8_t fontx2_open( uint8_t fontnum, char* filename );
フォントファイルを開き、ヘッダを読み込む。
(指定したファイル名のフォントを、指定したフォント番号に割り当てる)
fontnum = フォント番号 filename = ファイル名へのポインタ
戻り値:正常終了で0, エラーのときエラーコード
void fontx2_close( uint8_t fontnum );
フォントファイルを閉じる。
(指定したフォント番号のフォントファイルを閉じ、使用を終了する)
fontnum = フォント番号
uint8_t fontx2_read( uint8_t* buffer, uint8_t fontnum, uint16_t charcode );
フォントデータを取得する。
buffer = 読み込むバッファへのポインタ fontnum = フォント番号 charcode = 文字コード
戻り値:正常終了で0, エラーのときエラーコード
fontx2.c 関数戻り値(エラーコード)一覧:
FONTX2_OK : 正常終了(0)
FONTX2_ERR_OPEN : フォントファイルオープンエラー(1)
FONTX2_ERR_RHD : フォントヘッダリードエラー(2)
FONTX2_ERR_RCB : コードブロックリードエラー(3)
FONTX2_ERR_FNO : フォント番号不正(4)
FONTX2_ERR_UNDEF : 未定義の文字コード(5)
FONTX2_ERR_FSIZE : 未対応フォント(1文字当たりのデータサイズがFONTX2_FONTSIZEより大きい)(6)
FONTX2_ERR_FSEEK : フォントデータシークエラー(32 + f_lseek 関数の終了コード)
FONTX2_ERR_FREAD : フォントデータリードエラー(64 + f_read 関数の終了コード)
FONTX2_ERR_TRUNC : フォントデータリードエラー(128 + 読み込めたバイト数)
最後に実行例の写真を載せておきます。

Longan Nanoで日本語の表示が可能になると、かなり実用的ですね。好みのフォントも使えますので、表現の幅も広がります。急ごしらえで作成したライブラリなので処理速度などの点で最適化することはまだまだできると思いますが、興味のある方は使用してみていただければと思います。
Longan NanoのLCD制御ライブラリを眺めていて、比較的簡単にオリジナルフォントの表示を行うことができそうに思えましたので、さっそく改造してみました。フォントデータとして、扱いやすく入手の容易なFONTX2形式に対応し、半角文字だけでなく漢字を含む全角文字の表示にも対応してみました。FONTX2のデータはサイズが大きいので、マイコンのフラッシュメモリではなくmicroSDカードに置きたいところですが、そのためにはFATファイルシステムへの対応が必要となります。幸いにもSipeed社のサンプルにはmicroSDカードから画像データを読み込んで表示するためにChaN様作のFatFsが組み込まれており、今回はこれを流用しました。ただ、Sipeed社の開発した低レベルI/Oのコード(tf_card.c 内の rcvr_spi_multi 関数)にバグがあり、そのままだとバッファオーバーランを起こして漢字フォントの表示に失敗するため、修正を行っています。最初はこの不具合に気づかず、自分の書いたコードを何度も見直して何日も無駄にしてしまいました…。今回は、日本語の文字列に対応するためUTF-8を利用できるようにしましましたが、みいちゃん様によるUTF8→UTF16の変換アルゴリズムと、FatFsに含まれるUTF16→JISへの変換関数(ffunicode.c)を改変して利用させていただきました。また、FONTX2データのアドレス計算を行う部分も、ChaN様のサイトで公開されているものを利用させていただきました。PlatformIO(GigaDevice GD32V SDK)用のライブラリとサンプルプログラムのソースコードはGitHubにて公開しています。
以下に、利用方法を書いておきます。
1. microSDカードにFONTX2のフォントデータを保存します。
2. include/lcd/lcd.h を編集し設定を行います。FONTX2関連(+α)の設定は以下の部分で行います。
#define USE_FONTX2 2 // FONTX2の設定(0:不使用 1:2バイト文字のみ 2:1バイト文字も)
#define LCD_AFONT "PAW16A.FNT" // 1バイトフォントファイル名(ぱうフォント半角)
#define LCD_KFONT "PAW16K.FNT" // 2バイトフォントファイル名(ぱうフォント全角)
#define LCD_ASPACE 0 // FONTX2 1バイトフォントの横方向スペーシング
#define LCD_KSPACE 0 // FONTX2 2バイトフォントの横方向スペーシング
#define FONT_HEIGHT 17 // 自動改行時の1行の高さ
#define USE_UTF8STR // UTF-8の文字列を扱う場合に定義
#define FONTX2_USELED // LEDをステータス表示に使用する場合に定義
#define LCD_COMPAT // 関数名の互換性指定
USE_FONTX2 でFONTX2を使用するかどうかを指定します。設定値は以下のとおりです。
0:FONTX2を使用せず、oledfont.h で定義されたフォントのみを使用します
1:2バイト文字のみFONTX2を使用し、1バイト文字はoledfont.h で定義されたフォントを使用します
2:1バイト文字、2バイト文字ともFONTX2を使用します
FONTX2を使用する場合には、1バイト文字フォントのファイル名を LCD_AFONT で、2バイト文字フォントのファイル名を LCD_KFONT で指定します。
LCD_ASPACE, LCD_KSPACE で、FONTX2フォントで文字列表示を行う場合の水平方向の文字間隔(右側のスペーシング)をドット数で指定できます。また、FONT_HEIGHT で、文字列表示の際の自動改行時の1行の高さを指定できます。
USE_UTF8STR を定義すると文字列表示を行う際に日本語の文字列(UTF-8)を使用できますが、その場合にはUnicodeからShift JISへの変換テーブルが組み込まれるためコードサイズが大きくなります。
FONTX2_USELED を定義するとオンボードLEDでステータス表示を行います。フォントデータの読み込みエラー時に赤、SDカードへのアクセス中は緑に点灯します(主にデバッグ用です)。
LCD_COMPAT は、Sipeed社提供のオリジナルライブラリと互換性のある関数名を使用する場合に定義します。というのも、オリジナルのライブラリのほとんどの関数は“LCD_”から始まるのですが、LCD初期化関数 Lcd_Init と、円を描画する Draw_Circle だけが例外となっており統一感がなかったため、今回作成したものではそれぞれ LCD_Init, LCD_DrawCircle に改名しました。ただ、そのままだとこれまで開発したプログラムが動作しなくなるため、この定数を定義することによって旧関数名でも利用できるようにしています。
3. include/lcd/fontx2.h で扱うフォントの1文字当たりのデータサイズを指定します。
#define FONTX2_FONTSIZE 72 // フォントデータの最大サイズ(24x24→72バイト)
4. サンプルの main.c と同様に、lcd.h をインクルードします。
以上の手順でOKです。以下に簡単に関数のリファレンスを記しておきます。
lcd.c グローバル変数:
unsinged int BACK_COLOR; :文字表示の際の背景色
unsigned char image[12800]; :LCD_ShowPicture() で表示するイメージを格納する配列
unsigned char ankfont_width; :半角文字の横方向ドット数
lcd.c 関数 (低レベルI/O関数は除く):
void LCD_Init(void); ( = Lcd_Init )
LCDを初期化する。
void LCD_Clear(u16 Color);
LCDをクリアする(=Colorで指定した色で全面を塗りつぶす)。
Color = 背景色
void LCD_ShowChinese(u16 x,u16 y,u8 index,u8 size,u16 color);
漢字を表示する(別途 oeldfont.h の中でパターン定義されているもののみ)。
x, y = 座標 index = 漢字のインデックス番号(定義されている順に0~)
size = フォントサイズ(16/32) color = 描画色
void LCD_DrawPoint(u16 x,u16 y,u16 color);
点を打つ。
x, y = 座標 color = 描画色
void LCD_DrawPoint_big(u16 x,u16 y,u16 color);
大きな点を打つ(実際には3x3ピクセルの正方形を描く)。
x, y = 座標 color = 描画色
void LCD_Fill(u16 xsta,u16 ysta,u16 xend,u16 yend,u16 color);
矩形領域を塗りつぶす。
xsta, ysta = 左上座標 xend, yend = 右下座標 color = 描画色
void LCD_DrawLine(u16 x1,u16 y1,u16 x2,u16 y2,u16 color);
直線を描画する。
x1, y1 = 起点座標 x2, y2 = 終点座標 color = 描画色
void LCD_DrawRectangle(u16 x1, u16 y1, u16 x2, u16 y2,u16 color);
矩形を描画する。
x1, y1 = 起点座標 x2, y2 = 終点座標 color = 描画色
void LCD_DrawCircle(u16 x0,u16 y0,u8 r,u16 color); ( = Draw_Circle )
円を描画する。
x0, y0 = 中心 r = 半径 color = 描画色
void LCD_ShowChar(u16 x,u16 y,u16 num,u8 mode,u16 color);
文字を表示する
x, y = 座標 num = 文字コード(ASCII/SJIS) mode = モード color = 描画色
※2バイト文字の場合はShift-JISコードを指定してください
※mode = 1 のとき OR、 mode = 0 のとき 上書き(文字背景を BACK_COLOR で塗りつぶした上に描画)
void LCD_ShowString(u16 x,u16 y,const u8 *p,u16 color);
文字列を表示する(必ず上書きモードとなる)。
x, y = 座標 p = ASCII/UTF8文字列へのポインタ color = 描画色
void LCD_ShowNum(u16 x,u16 y,u16 num,u8 len,u16 color);
整数値を表示する。
x, y = 座標 num = 数値 len = 表示文字数 color = 描画色
void LCD_ShowNum1(u16 x,u16 y,float num,u8 len,u16 color);
小数値を表示する。
x, y = 座標 num = 数値 len = 表示文字数 color = 描画色
void LCD_ShowPicture(u16 x1,u16 y1,u16 x2,u16 y2);
イメージを表示する( image[] に格納されたデータを表示する)。
x1, y1 = 左上座標 x2, y2 = 右下座標
void LCD_ShowLogo(void);
ロゴマークを表示する( bmp.h で定義されたデータを表示する)。
void LCD_ShowHex(u16 x,u16 y,u16 num,u8 len,u16 color)
16進数を表示する
x, y = 座標 num = 数値 len = 表示文字数 color = 描画色
fontx2.c グローバル変数:
FH font[FONTX2_FONTNUM];
FONTX2ヘッダ読み込みバッファ
(FH は、FONTX2ヘッダ構造体)
uint8_t fontdata[FONTX2_FONTSIZE];
フォントパターン格納バッファ
fontx2.c 関数:
void fontx2_init( void );
FONTX2ライブラリを初期化する。
uint8_t fontx2_open( uint8_t fontnum, char* filename );
フォントファイルを開き、ヘッダを読み込む。
(指定したファイル名のフォントを、指定したフォント番号に割り当てる)
fontnum = フォント番号 filename = ファイル名へのポインタ
戻り値:正常終了で0, エラーのときエラーコード
void fontx2_close( uint8_t fontnum );
フォントファイルを閉じる。
(指定したフォント番号のフォントファイルを閉じ、使用を終了する)
fontnum = フォント番号
uint8_t fontx2_read( uint8_t* buffer, uint8_t fontnum, uint16_t charcode );
フォントデータを取得する。
buffer = 読み込むバッファへのポインタ fontnum = フォント番号 charcode = 文字コード
戻り値:正常終了で0, エラーのときエラーコード
fontx2.c 関数戻り値(エラーコード)一覧:
FONTX2_OK : 正常終了(0)
FONTX2_ERR_OPEN : フォントファイルオープンエラー(1)
FONTX2_ERR_RHD : フォントヘッダリードエラー(2)
FONTX2_ERR_RCB : コードブロックリードエラー(3)
FONTX2_ERR_FNO : フォント番号不正(4)
FONTX2_ERR_UNDEF : 未定義の文字コード(5)
FONTX2_ERR_FSIZE : 未対応フォント(1文字当たりのデータサイズがFONTX2_FONTSIZEより大きい)(6)
FONTX2_ERR_FSEEK : フォントデータシークエラー(32 + f_lseek 関数の終了コード)
FONTX2_ERR_FREAD : フォントデータリードエラー(64 + f_read 関数の終了コード)
FONTX2_ERR_TRUNC : フォントデータリードエラー(128 + 読み込めたバイト数)
最後に実行例の写真を載せておきます。

Longan Nanoで日本語の表示が可能になると、かなり実用的ですね。好みのフォントも使えますので、表現の幅も広がります。急ごしらえで作成したライブラリなので処理速度などの点で最適化することはまだまだできると思いますが、興味のある方は使用してみていただければと思います。
- 関連記事
-
- Sipeed Longan NanoでFONTX2形式のフォントを利用する (2020/02/12)
- USB Blaster互換機でLongan Nanoのデバッグ環境構築 (2019/12/07)
- Sipeed Longan Nanoで文字を表示してみる (2019/11/10)
スポンサーサイト