勝手な電子工作・・

勝手なオリジナル電子工作に関する記事を書きます

オリジナル・レーザープロッター その1-刻印データ作成プログラム

刻印したい画像をパソコンでデータ・ファイルに変換し、SDカードに書き込んで本体のArduinoの入力にします。Arduinoではそれを単純に読み込みながら刻印するだけというのが最も単純でらくちんかと思います。

f:id:a-tomi:20190810152805j:plain

Arduino側で刻印時に加工できないこともないわけですが、好き勝手な機能、たとえば操作時間短縮のために往復刻印するとか、好きな出力順序にするとかには、事前にPC側で処理しておくのが簡単ですし、PC側なら処理する際の容量に制約が生じません。PC空間なので、入出力が面倒ならファイル全部よんでしまうとかも自由自在ですし。

画像の形式はJPGやBMPですが、ここでご紹介する簡単な言語(フリー)では、画像種類がもっとたくさん扱えるかもしれません。しかし必要性がないので筆者は試したことがありません。こんなところで汎用の画像処理をする必要もないので、JPGだけとかBMPだけとかに決めておけばシンプルですね。「ないよりもあった方が良い」程度の機能は、ない方が良いのが常ですから。

 

Microsoft Small Basicのご紹介

PCなら、PythonでもCでも何の言語でも使えます。しかしWindowsの場合に、ちょっとプログラムを作るのに一番単純で手っ取り早いのはMS Small Basicだと筆者は勝手に思います。作成時間が僅かで済むし、WIndows自体との相性もいい、、、それはあたりまえか。

pythongccなど便利な言語はたくさんあり、筆者も普段はそれらを使っていますが、関連するコンポネントと相性が悪いことなどで環境の整合性確保に手間がかかることがあります。VB等は過去のバージョンアップ都度の互換性対応で少し嫌い・・^^;

現在は、仕事や複雑なものを作るとき以外、つまり趣味にはこの単純なSmallBasicを使うことが多く、時間もエネルギーも最小でたいへん楽をしています。

f:id:a-tomi:20190810155827j:plain

なにせ、型とか変換とかの概念がなく、数値は常に最大桁数精度であるなどの超単細胞(!)が筆者から見ると却って魅力的。それどころか文字と数値との区別すらありません!よって機能はとても限られていますが、使う側の工夫次第。例えば多次元の配列がないため代わりに1行のベクトル内に自分で作るとか。その代わり、わけのわからない環境設定問題に遭遇しないのがたいへん気楽です。つまり全ては自分の手の内というわけで。

言語としては構造化文だけでなく、言語としてはタブーとされがちなGoto(ジャンプ)が許容され、却って書きやすいこともある。逆にSwitch Case (Select When)がないためIf文を並べなければならないことがある。

なにはともあれマイクロソフトからダウンロードしてみてください。ネットでの情報は整っていて、手元にマニュアルが要らない簡単さです。

 

◎刻印プログラム内での処理

ソースプログラム例を下へ掲載しておきます。短時間で作ったもので、きれいなプログラムとはいえませんが。

処理内容としては

  1. 画像のある場所へのパスを定義。
  2. 指定画像を取り込む。
  3. 画像のサイズ情報を読むなどして、刻印データのヘダーとして出力。
  4. 画像の各ピクセルの色情報を輝度情報に変換して、一定輝度以下のところを刻印点にする。
  5. その座標(XY座標系でも他の座標系でも)を刻印データへ出力。
  6. ファイルの終わり情報(EOF)を書き込む

といったところでしょうか。

 

なお、Arduino側の処理を楽にするため、数値の桁をそろえてレフトゼロを入れるのですが、この言語だとこれが結構面倒。生活の知恵で必要な桁数の最小値を加算してアウトプットします(4桁なら1000を足せば必ず4桁に揃えられる)。使うArduino側でそれを引きます。

EOFにはタブーの9999をつかってます。そのため、画像の幅最大を8999ピクセルに制限しますが、 そんなに大きい画像は普通はないですね。

 


    myTitle="New Laser Cutter - Step-A V00"
'Text window
TextWindow.Title=myTitle+"          (c)2019 Akira Tominaga"
TextWindow.BackgroundColor="Black"

' Get data path
myPath=File.GetSettingsFilePath()
myPath=Text.GetSubText(myPath,1,8)
myPath=myPath+"Dat3\"

' Prepare picture
TextWindow.WriteLine("")
KeyIn:
TextWindow.Write("     Please type-in PIC#. -> ")
keyinN=TextWindow.Read()
myPict=myPath+"PIC"+keyinN+".bmp"
PsizeX=ImageList.GetWidthOfImage(myPict)
If PsizeX>18 Then
  Goto ImageOK
Else
  TextWindow.WriteLine("*** No such PCL#.bmp ***")
  Goto Keyin
EndIf
ImageOK:
PsizeY=ImageList.GetHeightOfImage(myPict)

'Graphics window
GraphicsWindow.BackgroundColor = "Black"
GraphicsWindow.Width=PsizeX*2
GraphicsWindow.Height=PsizeY
GraphicsWindow.Title=myTitle
inPic=ImageList.LoadImage(myPict)
GraphicsWindow.DrawImage(inPic,0,0)

'Setup output file
myFile=myPath+"PICT"+KeyinN+"A.txt"
lnum=1 'PsizeX,PsizeY characters with left zero
szX=PsizeX+1000
szY=PsizeY+1000
X=PsizeX/2
Y=PsizeY/2
CalcPC()
header=szX+","+szY+","+rx10+","+thetax10000
File.WriteLine(myFile,lnum,header)


'Get RGB data and convert them to Brightness data
For Y=0 To PsizeY-1
  'TextWindow.Write("Y=")
  'TextWindow.WriteLine(Y)
  For X=0 To PsizeX-1
    color=GraphicsWindow.GetPixel(X,Y)
    getkido() ' convert RGB to Brightness
    gcolor="#000000"
    txdata=""
    If kido<128 Then
      gcolor="#FFFFFF"
      CalcPC()  'Calculate Polar Coordinates
      'Set text data
      X4=X+1000
      Y4=Y+1000   
      txdata=X4+","+Y4+","+rx10+","+thetax10000
      lnum=lnum+1      
      File.WriteLine(myFile,lnum,txdata)
    EndIf
    GraphicsWindow.SetPixel(X+PsizeX,Y,gcolor)
  EndFor
EndFor  
lnum=lnum+1
eof=9999
File.WriteLine(myFile,lnum,eof)

Sound.PlayBellRing()

Sub getkido
  HexV=0
  Hexc=Text.GetSubText(color,2,1)
  Hex2V()
  R10=HexV
  Hexc=Text.GetSubText(color,2,1)
  Hex2V()
  R01=HexV
  Rv=R10*16+R01
  Hexv=Text.GetSubText(color,3,1)
  Hex2V()
  G10=HexV
  Hexc=Text.GetSubText(color,4,1)  
  Hex2V()
  G01=HexV
  Gv=G10*16+G01
  Hexc=Text.GetSubText(color,5,1)
  Hex2V()
  B10=HexV
  Hexc=Text.GetSubText(color,6,1)
  Hex2V()
  B01=HexV
  Bv=B10*16+B01
  Kido=0.299*Rv+0.587*Gv+0.114*Bv
EndSub

Sub Hex2V 
  If Hexc < "9" Then
    Hexv=Hexc
  EndIf
  If Hexc="A" Then
    HexV=10
  EndIf
  If Hexc="B" Then
    HexV=11
  EndIf  
  If Hexc="C" Then
    HexV=12
  EndIf
  If Hexc="D" Then
    HexV=13
  EndIf   
  If Hexc="E" Then
    HexV=14
  EndIf
  If Hexc="F" Then
    HexV=15
  EndIf     
EndSub
  
Sub get4d  'change  chrN to chrN4
  getdigits()
  chrN4=chrNx
  if chrN<10 Then
    chrN4="000"+chrN4
  ElseIf chrN<100 Then
    chrN4="00"+chrN4
  ElseIf chrN<1000 Then
    chrN4="0"+chrN4
  EndIf
EndSub

Sub get5d  'change  chrN to chrN5
  getdigits()
  chrN5=chrNx
  if chrN<10 Then
    chrN5="0000"+chrN5
  ElseIf chrN<100 Then
    chrN5="000"+chrN5
  ElseIf chrN<1000 Then
    chrN5="00"+chrN5
  ElseIf chrN<10000 Then
    chrN5="0"+chrN5
  EndIf
EndSub

Sub getdigits
  gd=1
  gdl=Text.GetLength(chrN)
  chrNx=Text.GetSubText(chrN,gd,1)
Loopgd:
  If gdl<gd+1 Then
    Goto Outgd
  EndIf
  gd=gd+1
  chkd=Text.GetSubText(chrN,gd,1)
  If chkd="." Then
    Goto Outgd
  EndIf
  chrNx=Text.GetSubText(chrN,1,gd)
  Goto Loopgd
Outgd:
EndSub 

Sub CalcPC 'Calculate Polar Coordinates
  'from (x,y) to (rx10,thetax10000)
' Get values from origin
 xx=X-PsizeX/2     ' set orgin X0 to center
 yy=PsizeY+5-Y ' set origin Y0  to bottom+5(avoiding zero division)
' Calculate Radius
 r=Math.Power(Math.Power(xx,2)+Math.Power(yy,2),0.5)
 chrN=r*10
 get5d()
 rx10=chrN5+10000
 ' Calculate Theta
 theta=Math.ArcCos(xx/r)
 chrN=theta*10000
 get5d()
 thetax10000=chrN5+10000
 EndSub

 このプログラムでは任意の画像を刻印データに換えるわけです。

例えば次の左の図を処理すると刻印部として右の図が現れたらできあがり、鐘がチーンとなります。

f:id:a-tomi:20190810162205j:plain

出力ファイルは次のようになります。1行目はヘダーでXサイズ、Yサイズ、そして別のカッターメカニズムを使う場合のためのヘダーです。2行め以下は刻印データでX、Y、右の二つは別のメカニズム用のデータです。

f:id:a-tomi:20190810162755j:plain

もちろん白黒からでなくとも刻印データが得られます。次は一例。

f:id:a-tomi:20190810162934j:plain

別の回に書きますが、Arduino側での読み込みはごく単純で入出力域も1バイトしか要りません。

つまり、与えるデータの順番で刻印するだけですので、往復方向で刻印するには行うこのデータの順番を変えてやればよいだけです。

順番を変えた例は次の様なファイルです。順番変更は別のプログラムで行うのが簡単。処理はあっという間に終わります。なお90ステップほどで作った例は一応下に添付しますが、きれいなプログラムではありません。皆様お好きなようにおつくり下さい。Arduinoでは1バイトずつ読むので、こういう形式でも順次よみこむわけです。

f:id:a-tomi:20190810163538j:plain

 

両方向の刻印では次の動画のように高速です。メカに遊びがあるときは、一方向に揃えないとこうはいきませんね。

yutu.be

刻印ファイルを両方向刻印パターンへ変換するプログラムを次に示します。

これは自分用ですが、皆様もっと分かり易いプログラムをお作り下さいね。もちろん言語もどれでもよいわけなので。


    myTitle="New Laser Cutter - XY Step B  V00"
'Text window
TextWindow.Title=myTitle+"    (c)2019 Akira Tominaga"
TextWindow.BackgroundColor="Black"

' Get data path
myPath=File.GetSettingsFilePath()
myPath=Text.GetSubText(myPath,1,8)
myPath=myPath+"Dat3\"

' Prepare input file
TextWindow.WriteLine("")
KeyIn:
TextWindow.Write("     Please type-in PICT file#. -> ")
keyinN=TextWindow.Read()
InFile=myPath+"PICT"+keyinN+"A.txt"
InLnum=1
InText=File.ReadLine(InFile,InLnum)

TextWindow.Write("*******")
TextWindow.Write("InLnum=")
TextWindow.Write(InLnum)
TextWindow.Write("  InText=")
TextWindow.WriteLine(InText)

InChk=Text.GetSubText(InText,1,1)
If InChk>=1 Then
  Goto InFileOK
EndIf
Goto  KeyIn

InFileOK:
'Prepare output file
OutFile=myPath+"PICT"+KeyinN+"B.txt"

' Transfer header (XYsize+PCcenter)
OutLnum=1
OutText=InText
File.WriteLine(OutFile,OutLnum,OutText)
TextWindow.Write("OutLnum=")
TextWindow.Write(OutLnum)
TextWindow.Write("  OutText=")
TextWindow.WriteLine(OutText)

' Convert fileA to fileB
OutReverse=0
InLnum=InLnum+1
InText=File.ReadLine(InFile,InLnum)
TextWindow.Write("InLnum=")
TextWindow.Write(InLnum)
TextWindow.Write("  InText=")
TextWindow.WriteLine(InText)

OutText=InText
OutRec=1
Ysave=Text.GetSubText(InText,6,4)

MainLoop:
InLnum=InLnum+1
InText=File.ReadLine(InFile,InLnum)
TextWindow.Write("InLnum=")
TextWindow.Write(InLnum)
TextWindow.Write("  InText=")
TextWindow.WriteLine(InText)

InChk=Text.GetSubText(InText,1,4)
If Inchk=9999 Then
  OutLnum=OutLnum+1
  File.WriteLine(OutFile,OutLnum,OutText)
  TextWindow.Write("OutLnum=")
  TextWindow.Write(OutLnum)
  TextWindow.Write("  OutText=")
  TextWindow.WriteLine(OutText)
  OutText=InText
  OutLnum=OutLnum+1
  File.WriteLine(OutFile,OutLnum,OutText) 
  TextWindow.Write("OutLnum=")
TextWindow.Write(OutLnum)
TextWindow.Write("  OutText=")
TextWindow.WriteLine(OutText)
  Goto Eof
EndIf

Ychk=Text.GetSubText(InText,6,4)
If Ychk=Ysave Then
  Goto accumText
Else
  Ysave=Ychk
  Goto accumOut
EndIf

accumText:
If OutReverse=0 Then
   OutText=Text.GetSubText(OutText,1,23*OutRec-2)+"##"+InText 
Else
  OutText=Text.GetSubText(InText,1,21)+"##"+OutText
EndIf
OutRec=OutRec+1
Ysave=Ychk
Goto MainLoop

accumOut:
OutLnum=OutLnum+1
File.WriteLine(OutFile,OutLnum,OutText)
TextWindow.Write("OutLnum=")
TextWindow.Write(OutLnum)
TextWindow.Write("  OutText=")
TextWindow.WriteLine(OutText)
OutText=InText
If OutReverse=0 Then
  OutReverse=1
Else
  OutReverse=0
EndIf
Goto MainLoop

Eof:
TextWindow.Writeline("Eof")
Sound.PlayBellRing()

 

なお、作製したプロッターより大きな画像を使うときはArduino側に倍率パラメータを設ければ問題なく使えますが別の回に説明したいと思います。ただしメカの動きが端を超えないようにリミッターをつけるとよいですね。これも別の回に書きます。

f:id:a-tomi:20190810164032j:plain


では、今回はこのへんで。

 記事の最後に注意書きを入れました。必ずご覧くださいね。

回路等は前回の記事にあります:

a-tomi.hatenablog.com

 次に本体のプログラムがあります。

a-tomi.hatenablog.com

 次に基板の作り方などがあります。

a-tomi.hatenablog.com

以下、重要な注意です。レーザーは失明や傷害、火災等の危険を伴い、周囲にも危険がおよびます。関連法規と取扱い基準(http://kikakurui.com/c6/C6802-2011-01.html)などに従って、正しく管理してください。この記事をみて自作される場合も全て自己責任でお願いします。

©2019 Akira Tominaga, All rights reserved.