剛好趁最近研究進度不錯,可以偷懶一下做點小玩具~
外加UEC回收場裡面堆著一大堆廢棄電腦、印表機、各種想的到,想不到的東西,學校所有報廢的東西都會丟這邊,可以自由拿取,既然有這麼豐富的資源可以利用,不然就來做一台以前一直很想做的CNC時鐘,也為未來想做的CNC鋪路。
成果影片
成果影片
材料:
光碟機*2
磁碟機*1
LCD1602*1
Ardunio NANO*1
ATMEGA328P*1
SN754410*3
從光碟機拆下來的開關*5
一些木板、螺絲與螺帽
礙於一周只開一天,所以就多撿幾台光碟機以防萬一,前前後後撿了7台光碟機、2台磁碟機。
全部拆到剩下骨架,做CNC平衡度問題挺麻煩的,剛好光機上面需要的東西都有了~
用直流馬達控制讀頭的光碟機,礙於他的控制晶片datashhet找不到,自己做控制又花時間(想要快速開發),所以就再去回收場多撿幾台﹐確保有光碟機是用步進馬達控制的
再來就是一步一步的寫程式與測試了~
因為手邊沒有邏輯分析儀或示波器,就先用4顆LED代替,開始先用低頻<10Hz測試,沒問題後再慢慢調高頻率,測試馬達極限,Arduino有很方便的ADC可以用,直接設定大範圍,然後用可變電阻直接調控頻率,就可以省去反覆的測參數的時間。
步進馬達測試完成後,再加上從其他光碟機拆下來的開關,直接用熱溶膠黏上做極限開關,用作安全機制以及開機校正用,習慣上會加上LED方便debug用,以及防止一些基礎錯誤。
再來就是把整台機器架起來啦~
因為烙鐵是跟人借的,沒有借烙鐵架,索性就用光碟機的外殼架起來,剛好有鎖螺絲的缺口,烙鐵放在上面就卡住,所以很安全~
右邊的是鋸成兩半的磁碟機,做Z軸使用(畫/不畫)
因為測試方便所以全部都用單心線,怕在測試過程中拉扯會把焊點扯掉,所以在馬達或是極限開關把線拉出來後,都用熱熔膠黏好,這樣子在怎麼扯都只會拉到熱熔膠而以~
這是Y軸需要一個可以畫畫的平台,要一個比較平的平面,不然在畫的過程中可能會有些地方畫的到,有些地方畫不到,如果這樣子就要用到Z軸補償,軟體寫起來頗麻煩的,所以開始到處翻拆的屍體們,到底有哪個零件可以用
找老半天,終於找到就是他了!!!
筆電光碟機的無刷馬達,後面有個很好的平面~~ 說不定未來會控制無刷馬達後,他就是第四軸了XDD
因為還有些不合,所以經過各種銼刀洗禮加上熱熔膠,終於弄上去了~
Z軸上面要做點延伸臂,這樣子才能畫到整個平面
三顆馬達原本連結出來的金手指防止短路全部都用紙膠帶貼起來
完成!!!
再來就把原本寫好的馬達控制模組,同時控制X、Y軸,然後程式又是一番修改
殘念的是,當時只有兩顆馬達驅動IC(SN754410),跑去秋葉原買太遠外加這時間已經關門了...,但想繼續測Z軸,想說身上還有其他顆馬達驅動IC不然就試試看,然後就出現這種奇怪的景象XDD 測了幾下才發現,邏輯是錯的這顆IC不適合這狀況,只好作罷...,寫其他東西
隔天馬上殺去秋葉原買,然後可以三軸一起控制,畫簡單的直線了~
然後先獨立測試另外燒一顆Ardunio和LCD控制
完成後,開始弄寫數字、時間計數、LCD設定時間與兩個晶片協同控制
大功告成!!!
再來是焊成電路板...
因為這邊沒洗板機,只好焊洞洞板 先將電路圖草稿畫下來,記憶力不太好,常常在麵包板上面加加減減的,會忘記接腳在哪,然後把零件大略擺一下,確定需要的洞洞板大小然後裁切,裁切洞洞板只要用斜口鉗用力剪兩端下去,再來用折的即可,如果不放心用鋸子也可,速度會慢一點就是了
LCD控制板,焊完的樣子
後完後,要先用電表量正負極有沒有短路,IC、LCD電源正負極有沒有錯,這樣子至少IC、LCD裝上去通電後,不會燒掉
小心駛得萬年船~~~ 然後再測功能正不正確
CNC主控制板
擺好需要的零件怕忘記XDDD
CNC主控版正面,用Arduino NANO可以簡化很多電路~ 以後程式要改,還可以直接燒錄或是拉線出來燒LCD控制板的程式
2pin插頭中塞個排阻偷空間剛剛好XDD
上面三顆LED分別是三軸的第一支接腳,可以大概看一下現在是哪一軸在動
各種亂啊...,邊焊電路還要編整理線,先焊完IC的正負極,用電表檢查一遍,全部焊完,全部在檢查一遍,一通電就正常運作 開心~~ 超級怕,一個沒注意,把Arduino NANO燒了,這樣子就要等一兩天的時間買零件了...
再來是將所有的單心線換成多蕊線,但是拔之前熱熔膠黏的地方有些麻煩,所以就直接剪掉熱熔膠之後的單心線用多蕊線延長,然後拿熱縮套管保護好,看起來就很整齊啦~
程式
如果要能include header檔的話,要將整個開發資料夾放在 C:\Users\MCS51\Documents\Arduino\libraries
下面才能使用 原本要將整個控制程式寫成.cpp和.h檔的,但是試了幾下弄不起來 arduino自己本身的問題,所以要include自己寫的要另外弄 後來上網大概有查到方法,但看起來有點麻煩,所以目前就把所有東西都寫在同一個檔案裡啦,改天有時間再來改,全部程式塞在同一個檔案裡,寫起來頗累人的,也看得有點亂就是了....
主程式架構圖
先透過RS232交握訊後後, 收到幾點幾分幾秒(xx:xx:xx),然後做資料處理再開 啟對應的畫數字函數,更新的是三軸方向與位置(全域變數) timerIsr是timer中斷,透過調整中斷觸發時間改變畫圖的速度,當三軸位置與方向更新後將輸出至馬達。 因為只是做小玩具,所以極限開關的部分只用軟體端限制,放在最後輸出的地方,減少會發生不明bug的可能性XD
CNC時鐘主控制板電路圖大致上長這樣,SN754410(馬達驅動IC),有VCC1、VCC2兩個電源,VCC1是IC主要工作的電源接上Arduino的VCC即可,VCC2是提供馬達的電源,可以一樣可以不同看需求,在這邊是用外部的變壓器直接提供5V@2A,另外一端拉到Arduino NANO Vin的接腳上,這樣子電源會先經過穩壓IC才提供給Arduino晶片,因為手邊只有5V@2A的變壓器,透過穩壓IC後會有0.7V的壓降,所以提供Arduino晶片只有4.3v左右,還好ATMEGA328p的工作電壓是1.8V~5.5V,所以可以運作,只是因為用同樣的電壓提供LCD就會有點暗,LCD控制板的電路接法可以參考這篇
按鈕旁要個10k電阻接地,不然當按鈕沒有按下時,會變成空接訊號就會亂跳
LCD計數器架構圖 兩個外部中斷 debounceInterrupt1是設定/開始計數的按鈕 debounceInterrupt2是選擇設定哪一位的時間 時間計數交由timer中斷處理,為了測試方便設定500ms加一秒 所以時間會跑得比較快,最後將資料透過RS232傳到主控板上
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const byte OneCnt[] = { 0x01, 0x02,0x04, 0x08 }; | |
const byte TwoCnt[] = { 0x03, 0x06,0x0c, 0x09 }; | |
const byte OneTwoCnt[] = {0x01, 0x03, 0x02, 0x06, | |
0x04, 0x0c, 0x08, 0x09 }; |
GoHome()是每當機器啟動時,3軸會初始化回到原點 第7、8行是timer中斷初始化 第9行是機器啟動時告訴LCD控制板,可以開始傳資料了
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
DDRB = 0x0f; //pin 0~7 | |
DDRC = 0x0f; //pin A0~A5 | |
DDRD = 0x3f; //pin 8~13 | |
GoHome(); | |
Timer1.initialize( 5000 ); //slow 50k, med 10k, fast 5k | |
Timer1.attachInterrupt( timerIsr ); | |
Serial.print( "OK" ); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
StrData = Serial.readString(); | |
StrData.toCharArray( InData, StrLenMax ); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
while(1) | |
{ | |
delay(50); | |
if( CNC.xOk && CNC.yOk && CNC.zOk ) | |
{ | |
cntTime++; | |
CNC.temp = cntTime; | |
break; | |
if( cntTime > 10 ) break; | |
if( (CNC.xSet==CNC.xPos) && (CNC.ySet==CNC.yPos) && (CNC.zSet==CNC.zPos) ) | |
break; | |
} | |
cntTime++; | |
if( cntTime > 10 ) break; | |
CNC.temp = cntTime; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
void runMotorX( int dir ) | |
{ | |
boolean XSW = RangeLimit( "Xaxis", dir ); | |
PORTD = ( LimitSW( XSW, "Xaxis", runMode, dir ) ) << 2; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
void runMotorX( int dir, int xcoord ) | |
{ | |
if( (dir>0) && (CNC.xPos >= xcoord) ) CNC.xOk = true; | |
else if( (dir<0) && (CNC.xPos <= xcoord) ) CNC.xOk = true; | |
else CNC.xOk = false; | |
boolean XSW = RangeLimit( "Xaxis", dir ); | |
if( (dir > 0) && (CNC.xPos < xcoord) ) | |
{ | |
PORTD = ( LimitSW( XSW, "Xaxis", runMode, dir ) ) << 2; | |
} | |
else if( (dir < 0) && (CNC.xPos > xcoord) ) | |
{ | |
PORTD = ( LimitSW( XSW, "Xaxis", runMode, dir ) ) << 2; | |
} | |
else | |
xMotorStop(); | |
} |
Z軸因為只有一個極限開關,所以另外一端是用計數器算出移動距離做限制
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
boolean RangeLimit( String StrAxis, int dir ) | |
{ | |
if( StrAxis == "Xaxis" ) | |
{ | |
int inHome = ( PIND & 0x80 ) >> XLimitHome; //read pin | |
int inEnd = ( PIND & 0x40 ) >> XLimitEnd; | |
if( (inHome == Xhome) && (inEnd != Xend) && (dir>0) ) | |
return true; | |
else if( (inHome == Xhome) && (inEnd != Xend) && (dir<0) ) | |
return false; | |
else if( (inHome != Xhome) && (inEnd == Xend) && (dir>0) ) | |
return false; | |
else if( (inHome != Xhome) && (inEnd == Xend) && (dir<0) ) | |
return true; | |
else | |
return true; | |
} | |
if( StrAxis == "Yaxis" ) | |
{ | |
int inHome = ( PINB & 0x20 ) >> YLimitHome; //read pin | |
int inEnd = ( PINB & 0x10 ) >> YLimitEnd; | |
if( (inHome == Yhome) && (inEnd != Yend) && (dir>0) ) | |
return true; | |
else if( (inHome == Yhome) && (inEnd != Yend) && (dir<0) ) | |
return false; | |
else if( (inHome != Yhome) && (inEnd == Yend) && (dir>0) ) | |
return false; | |
else if( (inHome != Yhome) && (inEnd == Yend) && (dir<0) ) | |
return true; | |
else | |
return true; | |
} | |
if( StrAxis == "Zaxis" ) | |
{ | |
int inHome = ( PINC & 0x10 ) >> ZLimitHome; //read pin | |
if( (inHome == Zhome) && (dir>0) ) | |
return true; | |
else if( (inHome == Zhome) && (dir<0) ) | |
return false; | |
else if( (CNC.zPos >= zStep) && (dir>0) ) | |
return false; | |
else if( (CNC.zPos >= zStep) && (dir<0) ) | |
return true; | |
else | |
return true; | |
} | |
} | |
byte LimitSW( int SW, String StrAxis, String StrMode, int dir ) | |
{ | |
if( SW > 0 ) return SwitchMode( StrAxis, StrMode, dir ); | |
else return 0; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
byte OnePhase( String StrAxis, int dir ) | |
{ | |
int CntTemp = SetSwitchAxisCnt( StrAxis ); | |
if( dir > 0 ) | |
{ | |
CntTemp++; | |
UpdataPos( StrAxis, dir ); | |
if( CntTemp > 3 ) CntTemp = 0; | |
} | |
else | |
{ | |
CntTemp--; | |
UpdataPos( StrAxis, dir ); | |
if( CntTemp < 0 ) CntTemp = 3; | |
} | |
GetSwitchAxisCnt( StrAxis, CntTemp ); | |
return OneCnt[CntTemp]; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
void timeFunc() | |
{ | |
if( itime.Hour >= 24 ) | |
{ | |
itime.Hour = 0; | |
HourStr = "00"; | |
} | |
if( itime.Min >= 60 ) | |
{ | |
itime.Min = 0; | |
MinStr = "00"; | |
itime.Hour++; | |
if( itime.Hour < 10 ) | |
HourStr = "0" + String( itime.Hour ); | |
else | |
HourStr = String( itime.Hour ); | |
} | |
if( itime.Sec >= 60 ) | |
{ | |
itime.Sec = 0; | |
SecStr = "00"; | |
itime.Min++; | |
if( itime.Min < 10 ) | |
MinStr = "0" + String( itime.Min ); | |
else | |
MinStr = String( itime.Min ); | |
} | |
if( itime.Sec < 10 ) | |
SecStr = "0" + String( itime.Sec ); | |
else | |
SecStr = String( itime.Sec ); | |
itime.Sec++; | |
} |
不知不覺就寫了一大篇文章XDD
大致上是這樣啦~
未來有時間的話,再來研究線性補間之類的,做一個真正的CNC~
P.S 第一次寫這種文章,格式各種跑掉,改天再來研究要怎麼寫才對...
太神啦~學長!!!
回覆刪除好玩具不做嗎~
刪除謝謝分享.
回覆刪除好強
回覆刪除在下有很多台光碟機
不嫌棄的話,可以送你喔
有燒錄器的嗎XDD
刪除最近才知道燒錄器裡面的雷射功率有到100mW~200mW
可以做一些有趣的應用~
版主您好!我不是本科生,在偶然機會下看到版主的文章,深受感動~我也想學習相關的知識和技能,請問我可以在哪裡找到呢?非常感謝您~
回覆刪除不太清楚指的相關技能是程式、電路還是機構?
回覆刪除做這個玩具需要的知識技能都是以前做的東西,一點一滴慢慢累積出來的~
有點難解釋怎麼一次全部學會所有的技能
不知道正不正確,但以前的做法是
如果想學會Ardunio怎麼寫或是電路怎麼設計,會先設定一個自己有興趣的目標
然後上網到處查資料慢慢拼湊出來,在過程中會發現有哪些需要加強的
單一時間專注一個點,一步一步慢慢來是最快的方式
以前常常把目標訂得太遠或是想要做到一步到位,每個都要做到最好
反而無疾而終
後來開始慢慢的了解"先求有再求好"
如果對Arduino有興趣可以參考這邊
http://coopermaa2nd.blogspot.jp/
如果想要做點小玩具可以參考這邊,常常逛這邊看別人是怎麼做機構設計的,有很多很有創意的想法~
http://www.instructables.com/
請問光碟機的步進馬達驅動電壓多少比較恰當呢?我是使用12v 沒10秒 步進馬達就發燙了??
回覆刪除我用5v驅動的
回覆刪除請問UEC回收場在哪啊?
回覆刪除UEC是日本電氣通信大學XDD
刪除