前回はM5Stackでペンプロッタを作ったのでストロークフォントで文字の描画に対応してみました
M5Stackでペンプロッタを作ってみた - Qiita
https://qiita.com/coppercele/items/8e35785da1dc717a0970
ストロークフォントを導入する##
こちらのサイトを参考にさせてもらいました
ストロークフォントを使って PlotClock で文字を描く – しかるのち
https://shikarunochi.matrix.jp/?p=4276
バイナリデータの解説があったのでこれを眺めてたら上位3bitで命令の種類を、
下位5bitで数値データを表してるんじゃね?と見当がついたので
Google spreadsheetで関数をこねこねしてみたらだいたいそんな感じでした
(0x20が終端だったりデータが飛んでたり例外も存在するのでそこは処理する)
まず参考にさせてもらうソースを改変してまるっと使わせてもらいます
※記事を書いた人はポインタが分かりません
// 戻り値で文字のバイト数を返すように変更
int efontUFT8toUTF16(uint16_t *pUTF16, char *pUTF8) {
// 1Byte Code
if (pUTF8[0] < 0x80) {
// < 0x80 : ASCII Code
*pUTF16 = pUTF8[0];
return 1;
}
// 2Byte Code
if (pUTF8[0] < 0xE0) {
*pUTF16 = ((pUTF8[0] & 0x1F) << 6) + (pUTF8[1] & 0x3F);
return 2;
}
// 3Byte Code
if (pUTF8[0] < 0xF0) {
*pUTF16 = ((pUTF8[0] & 0x0F) << 12) + ((pUTF8[1] & 0x3F) << 6) +
(pUTF8[2] & 0x3F);
return 3;
} else {
// 4byte Code
*pUTF16 = 0;
return 4;
}
}
void loop() {
M5.update();
// Aボタンを押すと描画
if (M5.BtnA.wasReleased()) {
std::string plotText = "あ";
Serial.printf("plotText:%s\n", plotText.c_str());
Serial.printf("plotText.length():%d\n", plotText.length());
for (int j = 0; j < plotText.length();) {
uint16_t strUTF16;
// fontIndex[]のindexを取得して文字のバイト数を取得する
int textBytes =
efontUFT8toUTF16(&strUTF16, (char *)(plotText.substr(j).c_str()));
Serial.printf("textBytes:%d\n", textBytes);
Serial.printf("strUTF16:%04x\n", strUTF16);
Serial.printf("plotText:%d:%s\n", j,
plotText.substr(j, textBytes).c_str());
// 複数バイト文字の場合ポインタをずらす
j += textBytes;
for (int i = 0; i < sizeof(fontIndex) / sizeof(fontIndex[0]); i++) {
if (strUTF16 == fontIndex[i]) {
// マルチバイト文字の場合textBytes分だけ関数に渡す
drawFont(fontDataIndex[i], textBytes);
break;
}
}
}
}
}
上位3bitをflag、下位5bitをdataとして扱います
なのでflagと命令の種類を==で判定します
void drawFont(const int *fontData, int textWidth) {
if (fontData == NULL) {
return;
}
int index = 0;
float x = 0;
float y = 0;
while (fontData[index] != 0x20) {
uint16_t flag = fontData[index] & 0b11100000;
uint16_t data = fontData[index] & 0b00011111;
if (flag == 0x20) {
// Xへ横移動
// move toX
if (6 < data) {
// 0x27が飛んでいる分引く
data--;
}
// 0x21が0なので引く
data--;
Serial.printf("Move toX=%d ", data);
// 移動するのでペンを上げる
penUp();
moveTo(x , y);
} else if (flag == 0x40) {
// 横移動のみで線を引く
if (27 < data) {
// 5Bから5Eまで飛んでいるので引く
data -= 2;
}
Serial.printf("Draw from here toX=%d\n", data);
// 線を引くのでペンを降ろす
penDown();
moveTo(x, y);
_GRBL.WaitIdle();
} else if (flag == 0xA0 || fontData[index] == 0x7E) {
// 0x60終点Xとして終点Yへ移動する
// Move toY
if (fontData[index] == 0x7E) {
// 0x7Eの例外処理
Serial.printf("toY=0\n");
data = 0;
} else {
Serial.printf("toY=%d\n", data);
}
// 移動するのでペンを上げる
penUp();
moveTo(x, y);
_GRBL.WaitIdle();
} else if (flag == 0x60) {
// 線を引く終点Xを指定
Serial.printf("Draw toX=%d ", data);
x = data;
} else if (flag == 0xC0) {
// 0x60を終点Xとして終点Yへ線を引く
// Draw toY
Serial.printf(" toY=%d\n", data);
penDown();
moveTo(x, y);
_GRBL.WaitIdle();
}
index++;
}
}
これで1文字書けるようになります
M5Stackペンプロッタでストロークフォントに対応しました
— もけ@ムギ㌠ (@coppercele) September 15, 2021
とりまAが描けたので明日以降複数文字を実装します(´・ω・`) pic.twitter.com/MbrAkp2Px3
複数文字に対応する##
複数文字を描画する場合は全角半角を判別してX軸のオフセットを管理すれば書けるようになります
#M5Stack ペンプロッタがストロークフォントの全角文字に対応しました\(^o^)/ pic.twitter.com/x7hjYQdzzX
— もけ@ムギ㌠ (@coppercele) September 19, 2021
詳しくはソースをご覧ください(丸投げ)
# include <ArduinoJson.h>
# include <M5Stack.h>
# include <string.h>
# include "GrblControl.h"
# include "fontData.h"
# define STEPMOTOR_I2C_ADDR 0x70
# define SERVO_ADDR 0x53
GRBL _GRBL = GRBL(STEPMOTOR_I2C_ADDR);
int degree = 0;
int PEN_UP = 130;
int PEN_DOWN = 107;
float x = 0.0;
float y = 0.0;
M5TreeView tv;
// addr 0x11 means "control the number 1 servo by angle"
void Servo_write_angle(uint8_t number, uint8_t angle) {
Wire.beginTransmission(SERVO_ADDR);
Wire.write(0x10 | number);
Wire.write(angle);
Wire.endTransmission();
}
void penUp() {
// servo(PEN_UP);
Servo_write_angle(0, PEN_UP);
delay(100);
// myservo.detach();
}
void penDown() {
Servo_write_angle(0, PEN_DOWN);
delay(100);
// myservo.detach();
}
void resetOrigin(char *axis) {
_GRBL.SetMode("distance"); // 相対移動モード
std::string str = "G1 1 F200";
str.insert(3, axis);
// USE Gcode
_GRBL.Gcode(const_cast<char *>(str.c_str())); // +1移動
str = "G1 -0.3 F200";
str.insert(3, axis);
while (!_GRBL.InLock()) {
_GRBL.Gcode(
const_cast<char *>(str.c_str())); // -0.2ずつロックされるまで移動
delay(100);
}
Serial.println("Locked");
Serial.println("Back to Origin");
str = "G1 0.2 F200";
str.insert(3, axis);
while (_GRBL.InLock()) {
_GRBL.UnLock(); // リミットスイッチのロックを解除する
_GRBL.SetMode("distance"); // absoluteに戻るので再設定
_GRBL.Gcode(const_cast<char *>(str.c_str()));
delay(300);
// スイッチが押しっぱなしだと即座にロックに戻るので移動してから少し待つ
}
_GRBL.SetMode("absolute");
Serial.println("Moved to Origin");
}
void moveTo(float toX, float toY) {
String command = "G1 X";
command += String(toX);
command += " Y";
command += String(toY);
command += " F200";
Serial.println(command.c_str());
_GRBL.Gcode((char *)(command.c_str()));
_GRBL.WaitIdle();
}
void tap() {
penDown();
penUp();
}
int efontUFT8toUTF16(uint16_t *pUTF16, char *pUTF8) {
// 1Byte Code
if (pUTF8[0] < 0x80) {
// < 0x80 : ASCII Code
*pUTF16 = pUTF8[0];
return 1;
}
// 2Byte Code
if (pUTF8[0] < 0xE0) {
*pUTF16 = ((pUTF8[0] & 0x1F) << 6) + (pUTF8[1] & 0x3F);
return 2;
}
// 3Byte Code
if (pUTF8[0] < 0xF0) {
*pUTF16 = ((pUTF8[0] & 0x0F) << 12) + ((pUTF8[1] & 0x3F) << 6) +
(pUTF8[2] & 0x3F);
return 3;
} else {
// 4byte Code
*pUTF16 = 0;
return 4;
}
}
int textOffset = 0;
void drawFont(const int *fontData, int textWidth) {
if (fontData == NULL) {
return;
}
int index = 0;
float x = 0;
float y = 0;
float scaleX = 4.0;
float scaleY = 20;
float fontWidth = 16;
if (1 < textWidth) {
textWidth = 2;
}
fontWidth /= scaleX;
while (fontData[index] != 0x20) {
uint16_t flag = fontData[index] & 0b11100000;
uint16_t data = fontData[index] & 0b00011111;
if (flag == 0x20) {
// Xへ横移動
// move toX
if (6 < data) {
data--;
}
data--;
Serial.printf("Move toX=%d ", data);
x = data / scaleX;
penUp();
moveTo(x + fontWidth * textOffset, y);
} else if (flag == 0x40) {
// 横移動のみで線を引く
if (27 < data) {
data -= 2;
}
Serial.printf("Draw from here toX=%d\n", data);
x = data / scaleX;.
penDown();
moveTo(x + fontWidth * textOffset, y);
_GRBL.WaitIdle();
} else if (flag == 0xA0 || fontData[index] == 0x7E) {
// 0x60終点Xとして終点Yへ移動する
// Move toY
if (fontData[index] == 0x7E) {
Serial.printf("toY=0\n");
data = 0;
} else {
Serial.printf("toY=%d\n", data);
}
y = data / scaleY;
penUp();
moveTo(x + fontWidth * textOffset, y);
_GRBL.WaitIdle();
} else if (flag == 0x60) {
// 線を引く終点Xを指定
Serial.printf("Draw toX=%d ", data);
x = data / scaleX;
} else if (flag == 0xC0) {
// 0x60を終点Xとして終点Yへ線を引く
// Draw toY
Serial.printf(" toY=%d\n", data);
y = data / scaleY;
penDown();
moveTo(x + fontWidth * textOffset, y);
_GRBL.WaitIdle();
}
index++;
}
penUp();
textOffset += textWidth;
}
void setup() {
M5.begin(true, true, true, true);
_GRBL.Init(400, 400, 400, 30);
_GRBL.SetMode("absolute");
dacWrite(25, 0); // ノイズ対策
delay(1000);
penUp();
}
void loop() {
M5.update();
if (M5.BtnA.wasReleased()) {
std::string plotText = "Hellow世界";
Serial.printf("plotText:%s\n", plotText.c_str());
Serial.printf("plotText.length():%d\n", plotText.length());
for (int j = 0; j < plotText.length();) {
uint16_t strUTF16;
int textBytes =
efontUFT8toUTF16(&strUTF16, (char *)(plotText.substr(j).c_str()));
Serial.printf("textBytes:%d\n", textBytes);
Serial.printf("strUTF16:%04x\n", strUTF16);
Serial.printf("plotText:%d:%s\n", j,
plotText.substr(j, textBytes).c_str());
j += textBytes;
for (int i = 0; i < sizeof(fontIndex) / sizeof(fontIndex[0]); i++) {
if (strUTF16 == fontIndex[i]) {
drawFont(fontDataIndex[i], textBytes);
break;
}
}
}
}
if (M5.BtnB.wasReleased()) {
resetOrigin("X");
resetOrigin("Y");
}
}