"廢物利用"也抄襲——“完全”DIY"繪圖儀"<二、下位機程序設計>
就不說怎麽組裝了吧,一把辛酸淚。說程序,因為這有兩把辛酸淚……一把給下位機的C代碼一把為了VB.NET的圖像處理……不過就上上一篇說的,它們可以正確運行了,並且今天克服了Arduino上電過程中步進電機沒事瞎轉悠的困難。
其實上位機和下位機的功能界定非常清晰:上位機解釋圖片為指令,下位機解釋上位機指令為硬件動作——就倆步進和一個激光器。當然,如果有讀卡器模塊,完全可以把命令寫成文件實現脫機打印。總體框架就是這樣,那麽下位機要實現的具體功能有哪些呢?
1、串口通訊:接收指令和發送請求。既然是通訊,校驗是少不了的,我寫了一點CRC8校驗。
2、控制步進電機:這方面的文章很多,夠學一會的。我修改了Stepper庫,當然只是用它的大部分框架,這個框架麽……哎
3、控制激光器:激光器這裏調節亮度的時候使用了PWM,正好手頭有若幹L298N…………
4、X,Y軸限位:用外部中斷來控制,需要註意的是,我用的Uno麽有那麽多中斷口可以揮霍,所以全部的微動開關都是連接在一起的。我是並聯的,所以未按下時應該時斷開的;如果串聯,那麽未按下時應該是閉合的。
5、軟復位功能:可以用軟件控制Arduino重啟,方法也搜了一些,有些看著高大上的卻然並卵。所以用的看門狗。
大體就是這樣吧,下面看一下部分代碼:
void setup() { Serial.begin(115200); AboveStepper.setSpeed(aSpeed); //設置上步進電機每分鐘轉數 BelowStepper.setSpeed(bSpeed); //設置下步進電機每分鐘轉數 AboveStepper.SetEnabled(true); //初始化完成完成其他初始化之後再開啟步進電機 BelowStepper.SetEnabled(true); attachInterrupt(InterruptIntID, Interrupt, CHANGE); //高電平 DoxGoto0(); DoyGoto0(); while (!Serial) {} Serial.println(r_Ready); }
一、初始化函數:這個函數在板子重啟後被運行一次。
a、首先初始化串口,需要註意的是,這個波特率在你的板子所支持的範圍內,越高越好——速度差異很大的。在這種頻繁收發數據的應用中,9600明顯感覺非常慢。
b、設置步進電機的轉速,然後開啟步進電機。
c、附加外部中斷,利用微動開關使x,y軸歸零。需要註意的是,如果你的板子加電時有擾動,那麽應該在附加外部中斷之前使x,y軸倒退一定的安全距離。
d、等待串口就緒,發送準備就緒信號。
二、外部中斷函數
void Interrupt() { if (digitalRead(InterruptIntPin) == HIGH) { CurState = 0; } else { if (CurState == 0) { //發生不應有的中斷 CurState = -1; AboveStepper.steps_left = 0l; //清理各個電機剩余步數 BelowStepper.steps_left = 0l; digitalWrite(LaserPin, 0); //關閉激光器 } else if (CurState == c_xGoto0) { CurState = -c_xGoto0; } else if (CurState == c_yGoto0) { CurState = -c_yGoto0; } else if (CurState == c_lzGoto0) { CurState = -c_lzGoto0; } else if (CurState == c_rzGoto0) { CurState = -c_rzGoto0; } } }
這個函數也非常清晰,當微動閉合時,證明某一個開關被觸動,如果是程序控制的,那麽更改當前狀態以便退出正在運行的循環;如果是意外中斷,那麽關閉相應的硬件避免損壞。這個函數應該盡可能短,它在極為有限的時間內就應調用完成,所以一般采用全局變量進行控制,這裏就是使用CurState。
三、運行時的“循環”函數——Loop
這個函數並不是一次運行的,它是被系統不斷的反復調用。我的代碼如下:
void loop() { if (CurState == 0 || CurState == State_Stop) { //非中斷狀態 if (Serial.available()>=msgBuffSize) { msgLen = Serial.readBytes(msgBuff, msgBuffSize); //讀取消息 if (msgBuff[msgBuffSize - 1] == cal_crc_table(msgBuff)) { CommandParsing(msgBuff); //處理消息 if (CurState != State_Stop) { RequestData(); //請求數據 } }else{ RerequestData(); } } } }
這裏添加了暫停的功能,所以看起來可能有點亂。首先在正常狀態或暫停狀態下,嘗試讀取串口獲取指令,當獲取到數據後,進行Crc8驗證,若未通過則重新申請數據;否則對命令進行解釋並執行,隨後當不處於暫停狀態時再次申請指令。
命令解釋器就不詳細說了,無非是一個大的分支結構。這裏簡要說一下這個AxiDraw用的雙電機結構是怎麽移動x,y軸的,其實很簡單,你裝起來之後用手轉轉就知道了。兩個電機不同時針方向運行控制一軸,兩個電機同方向運行控制另一軸。我的是這樣的(Y+,Y-代表Y軸正方向和負方向上的電機):
a、Y+順時針Y-逆時針→X軸向負方向運行
b、Y+順時針Y-順時針→Y軸向負方向運行
所以代碼是這樣的:
void DoxMove(long dBeat) { int dir, step; if (dBeat < 0) { dir = -1; step = -dBeat; }else{ dir = 1; step = dBeat; } for (int i = 0; i < step; i++) { AboveStepper.step(dir); BelowStepper.step(-dir); } } void DoyMove(long dBeat) { int dir, step; if (dBeat < 0) { dir = -1; step = -dBeat; }else{ dir = 1; step = dBeat; } for (int i = 0; i < step; i++) { AboveStepper.step(dir); BelowStepper.step(dir); } } void Do13Move(long dBeat) { AboveStepper.step(dBeat); } void Do24Move(long dBeat) { BelowStepper.step(dBeat); }
當然,完全可以不用For循環。但是走斜線的時候感官上好像“繞遠”,看著有點矬。然後是激光器控制,直接用PWM就可以了。最後,是軟重啟,用看門狗最通用,很穩定,無接線:
#include <avr/wdt.h> void Soft_ReStart(){ do{ wdt_enable(WDTO_15MS); //開啟看門狗計時器,然後不餵狗……就重啟了。 for (;;){ } } while (0); }
就是這……
"廢物利用"也抄襲——“完全”DIY"繪圖儀"<二、下位機程序設計>