擊上方“嵌入式應用研究院”,選擇“置頂/星標公眾號”
干貨福利,第一時間送達!
建議不是本行又感興趣的小伙伴們先看下面兩篇了解一下Marlin:
開源Marlin2.x源代碼架構學習筆記
3D列印機marlin韌體框架與GCode命令總結
YouTube上的老外通俗易懂的方式講解Marlin2.0
然后再看下面的文章,
1、摘要
說到 RepRaptor,我們就有必要來了解下RepRap 3D列印機,RepRap是人類歷史上第一部可以自我復制型的機器,RepRap是一部可以生成塑料實物的免費桌面型 3D 列印機,由于 RepRap 很多部件都是由塑料制成了,并且 RepRap 都可以進行列印,所以 RepRap 可以自我復制,也就是說,任何人都可以花一些時間收集夠材料進行制作,這也意味著,如果您已經有一臺 RepRap 了,您可以在列印很多有用的物件的同時,為朋友再列印出另一部 RepRap,RepRap 致力于自我復制型機器的制作,并無償的提供給大家使用,我們用 3D 列印來實作這些,但如果您使用其它的技術也實作了自我復制并愿意無償提供給大家使用,那么,這里也將非常歡迎您的加入,RepRap是第一款低成本 3D 列印機,并且 RepRap 還開創了開源 3D 列印機的革命,在手工制作類社區的所有成員中被廣泛使用的一款 3D 列印機,
關鍵詞:3D列印機;自我復制;RepRap;RepRaptor
2、RepRaptor簡介
RepRaptor是一個可用于支持GCode指令3D列印上位機,它是用QT5來撰寫的,之所以使用QT5來撰寫,這是因為開發者希望它能夠任何硬體上運行,因此,RepRaptor也可以用于控制RepRap 來實作3D模型的列印,它的界面是這樣的:

我們能夠看到的是,它已經具備了3D列印機上位機的基本雛形,目前3D列印機的主流的架構主要是這樣的:

因此,RepRaptor相當于充當了上位機這個部分的作業:

只可惜,這個版本僅僅發布到了RepRaptor v0.3.8以后就沒有再進行繼續更新了,

但是沒關系,我們可以基于這個雛形,做出屬于我們自己的3D列印機上位機,然后我們就可以買一臺支持聯機列印的3D列印機,愉快的進行模型列印了!
3、RepRaptor原始碼架構導讀
在開發屬于自己的3D列印機上位機之前,我們必須獲得它的原始碼:
git clone https://github.com/josefprusa/RepRapCalculator.git
然后使用QT Creator將其打開,接下來我們就可以看到下面的代碼結構:

3.1、界面介紹
在這里,我們可以看到這是一個基于QT Designer做UI界面,類似于MFC一樣拖控制元件,然后再使用C++寫邏輯,因此,我們能夠看到它是由6個UI界面來完成的,分別是:
(1) aboutwindow.ui
關于專案的介紹

(2)eepromwindow.ui
獲取列印機EEPROM中的資料并展現到界面上來,此部分功能不全,

(3) errorwindow.ui
一些運行錯誤的提示視窗

(4)mainwindow.ui
主頁面,用于控制列印機的常規操作、獲取列印機反饋的資訊,例如溫度、速度:

(5)sdwindow.ui
使用SD卡進行列印鎖需要的設定和檔案讀取等功能,此部分功能不全,

(6)settingswindow.ui
一些引數的設定,此部分功能不全

3.2、核心代碼架構導讀

4、打造屬于我們自己的3D列印機上位機
4.1、成功打造屬于我們自己的3D列印機上位機的前提
當然,想要學會打造自己的列印機的前提,你得具備以下基礎知識:
掌握QT軟體開發(如果你會C#或者其它當然也沒問題)
掌握3D列印機GCode指令協議
其它必要的知識,如設計模式、資料結構等,
4.2、核心互動邏輯的實作
關于GCode的格式以及回應的通訊協議可以參考:
https://marlinfw.org/meta/gcode/
這上面列出了幾乎所有Marlin支持的GCode協議指令,

從原始碼導讀部分,我們最需要關心的是mainwindow.cpp、sender.cpp和parser.cpp這三個檔案,因為它們是實作3D列印機上位機成功的基礎,這里我們能夠看到這三個執行緒之間的交集部分,也就是mainwindow.cpp里的這段代碼:
//Parser thread signal-slots and init
parserWorker->moveToThread(parserThread);
connect(parserThread, &QThread::finished, parserWorker, &QObject::deleteLater);
connect(this, &MainWindow::startedReadingEEPROM, parserWorker, &Parser::setEEPROMReadingMode);
connect(parserWorker, &Parser::receivedTemperature, this, &MainWindow::updateTemperature);
connect(parserWorker, &Parser::receivedSDFilesList, this, &MainWindow::initSDprinting);
connect(parserWorker, &Parser::receivedEEPROMLine, this, &MainWindow::EEPROMSettingReceived);
connect(parserWorker, &Parser::recievingEEPROMDone, this, &MainWindow::openEEPROMeditor);
connect(parserWorker, &Parser::receivedError, this, &MainWindow::receivedError);
connect(parserWorker, &Parser::receivedSDDone, this, &MainWindow::receivedSDDone);
connect(parserWorker, &Parser::receivedSDUpdate, this, &MainWindow::updateSDStatus);connect(parserWorker, &Parser::receivedNotSDPrinting, this, &MainWindow::receivedNotSDPrinting);
parserThread->start();
parserThread->setPriority(QThread::HighestPriority);
//Sender thread signal-slots and init
senderWorker->moveToThread(senderThread);
connect(senderThread, &QThread::finished, senderWorker, &QObject::deleteLater);
connect(parserWorker, &Parser::receivedOkNum, senderWorker, &Sender::receivedOkNum);
connect(parserWorker, &Parser::receivedOkWait, senderWorker, &Sender::receivedOkWait);
connect(parserWorker, &Parser::receivedResend, senderWorker, &Sender::receivedResend);
connect(parserWorker, &Parser::receivedStart, senderWorker, &Sender::receivedStart);
connect(senderWorker, &Sender::errorReceived, this, &MainWindow::serialError);
connect(senderWorker, &Sender::dataReceived, parserWorker, &Parser::parse);
connect(senderWorker, &Sender::dataReceived, this, &MainWindow::readSerial);
connect(senderWorker, &Sender::reportProgress, this, &MainWindow::updateFileProgress);
connect(senderWorker, &Sender::baudrateSetFailed, this, &MainWindow::baudrateSetFailed);
connect(this, &MainWindow::setFile, senderWorker, &Sender::setFile);
connect(this, &MainWindow::startPrinting, senderWorker, &Sender::startPrinting);
connect(this, &MainWindow::stopPrinting, senderWorker, &Sender::stopPrinting);
connect(this, &MainWindow::pause, senderWorker, &Sender::pause);
connect(this, &MainWindow::setBaudrate, senderWorker, &Sender::setBaudrate);
connect(this, &MainWindow::openPort, senderWorker, &Sender::openPort);
connect(this, &MainWindow::closePort, senderWorker, &Sender::closePort);
connect(this, &MainWindow::injectCommand, senderWorker, &Sender::injectCommand);
connect(this, &MainWindow::flushInjectionBuffer, senderWorker, &Sender::flushInjectionBuffer);
senderThread->start();
senderThread->setPriority(QThread::TimeCriticalPriority);
(1)執行緒互動的設計
當我們看懂了這段代碼以后就可以將它們抽象成我們自己的代碼,即分解為:

以下是我基于上面的這個架構進行了簡單的修改:
/*注冊串口執行緒*/
void MainWindow::Register_Uart_thread()
{
uartThread = new QThread(this);
uartWorker = new SerialThread(Sensor_Uart,Sensor_Baud);
uartWorker->moveToThread(uartThread);
connect(uartThread, &QThread::finished, uartWorker, &QObject::deleteLater);
connect(uartWorker, &SerialThread::reportProgress, this, &MainWindow::updateFileProgress);
connect(uartWorker, &SerialThread::update_layer, this, &MainWindow::updateModelLayer);
connect(uartWorker, &SerialThread::send_debug_gcode_line, this, &MainWindow::GCode_Debug);
connect(this, &MainWindow::startPrinting, uartWorker, &SerialThread::startPrinting);
connect(this, &MainWindow::stopPrinting, uartWorker, &SerialThread::stopPrinting);
connect(this, &MainWindow::pausePrinting, uartWorker, &SerialThread::pausePrinting);
connect(this, &MainWindow::recovery_print, uartWorker, &SerialThread::recovery_print);
uartThread->start();
uartThread->setPriority(QThread::TimeCriticalPriority);
}
/*注冊檔案管理執行緒*/
void MainWindow::Register_FileManage_thread()
{
fileThread = new QThread(this);
fileWorker = new file_manage();
fileWorker->moveToThread(fileThread);
connect(fileThread, &QThread::finished, fileWorker, &QObject::deleteLater);
connect(this, &MainWindow::setGodeFile, fileWorker, &file_manage::parseFile);
connect(fileWorker, &file_manage::update_gcode_info_display, this, &MainWindow::GCode_File_Info);
connect(fileWorker, &file_manage::send_gcode_file, uartWorker, &SerialThread::setFile);
fileThread->start();
fileThread->setPriority(QThread::HighestPriority);
}
/*注冊協議決議執行緒*/
void MainWindow::Register_Protocol_Parse_thread()
{
parserThread = new QThread(this);
parseWorker = new protocol_parse();
parseWorker->moveToThread(parserThread);
connect(parserThread, &QThread::finished, parseWorker, &QObject::deleteLater);
connect(parseWorker, &protocol_parse::hotbed_temp, this, &MainWindow::Display_HotBed_Temp);
connect(parseWorker, &protocol_parse::hotend_temp, this, &MainWindow::Display_Hotend_Temp);
connect(parseWorker, &protocol_parse::move_position, this, &MainWindow::Display_Move_Position);
connect(uartWorker, &SerialThread::Data_Process, parseWorker, &protocol_parse::Data_Process);
parserThread->start();
parserThread->setPriority(QThread::HighestPriority);
}
(2)列印GCode檔案與用戶發送GCode命令的核心實作

(3)協議決議的核心實作
關于協議決議部分,我依然采用的是正則運算式的方案來實作,例如對溫度部分的處理:
typedef struct
{
QString raw;
double e_c, b_c;
double e_t, b_t;
} TemperatureReadings;
//正則運算式獲取溫度
void MainWindow::Get_Temp_Test()
{
int p = 0;
QStringList data_list ;
TemperatureReadings r;
/*對于單噴頭列印機,它回復的資料格式是這樣的*/
QByteArray data = "T:41.88 /0.00 B:56.02 /60.00 @:0 B@:0 W:?";
QRegExp temperatureRegxp("-?(([0-9]\\d*\\.\\d*)|(0\\.\\d*[0-9]\\d*)|([0-9]\\d*))");
while ((p = temperatureRegxp.indexIn(data, p)) != -1)
{
data_list.append(temperatureRegxp.cap(1));
//上一個匹配的字串的長度
p += temperatureRegxp.matchedLength();
}
r.raw = QString(data);
r.e_c = data_list[0].toDouble(); //噴頭當前溫度
r.e_t = data_list[1].toDouble(); //噴頭目標溫度
r.b_c = data_list[2].toDouble(); //熱床當前溫度
r.b_t = data_list[3].toDouble(); //熱床目標溫度
qDebug() << "決議源字串:" << r.raw << " " << "決議長度:" << data_list.length();
qDebug() << r.e_c ;
qDebug() << r.e_t ;
qDebug() << r.b_c ;
qDebug() << r.b_t ;
}
例如對當前移動坐標的獲取:
typedef struct
{
QString raw;
double x,y,z ;
} CoreXYZ ;
//正則運算式獲取坐標
void MainWindow::Get_GCodeXYZ_Test()
{
int p = 0;
QStringList data_list ;
CoreXYZ r;
/*移動指令,例如決議下面這個指令*/
QByteArray data = "G1 F1200 X-9.914 Y-9.843 E3.36222";
QRegExp CoreXYZRegxp("-?(([XYZ]|[0-9]\\d*\\.\\d*)|(0\\.\\d*[0-9]\\d*)|([0-9]\\d*))");
while ((p = CoreXYZRegxp.indexIn(data, p)) != -1)
{
data_list.append(CoreXYZRegxp.cap(1));
//上一個匹配的字串的長度
p += CoreXYZRegxp.matchedLength();
}
r.raw = QString(data);
r.x = data_list[0].toDouble();
r.y = data_list[1].toDouble();
qDebug() << "決議源字串:" << r.raw << " " << "決議長度:" << data_list.length();
qDebug() << data_list[0];
qDebug() << data_list[1];
}
(4)其它功能的設計
這部分就發揮大家自己的想象了,我先做了個測驗版本,隨便搗鼓一下,已經能夠正常列印模型了:


目前這個專案還沒有開源,我還在持續的完善中,希望最后能夠助力一把 RepRapCalculator,希望加什么功能,大家可以在下方給我留言,
5、總結
要做屬于自己的列印機,需要掌握以下技能:
掌握QT軟體開發(如果你會C#或者其它當然也沒問題)
掌握3D列印機GCode指令協議
其它必要的知識,如設計模式、資料結構等,
另外,讓我們對3D列印開源的小伙伴們為3D列印開源發發力量!
6、參考文獻 & 參考
[1] G-Code Index. (n.d.). Marlin Firmware. https://marlinfw.org/meta/gcode/
[2] Neo, T. (2017, May 27). G-Code Index. Github. https://github.com/NeoTheFox/RepRaptor
[3] Neo, T. (2017, May 27). A Qt RepRap Gcode Sender/Host Controller. https://opensourcelibs.com/lib/repraptor
[4]盧華東. (2015, December 26). RepRap 3D Printer 入門介紹. https://blog.csdn.net/lu_embedded/article/details/50409510
往期精彩
開源Marlin2.x源代碼架構學習筆記
步進電機驅動在3D列印應用的學習筆記(一)
3D列印程序與最近的學習成果
兩個最常用的3D列印機切片軟體
3D列印機marlin韌體框架與GCode命令總結
3D列印機Marlin韌體串口功能決議和程式移植
讓野火F103開發板支持Marlin2.0韌體是什么體驗?3D列印主控板成員+1
C語言映射表在嵌入式串口決議、UI設計中的應用(值得收藏并實踐的精華帖)
覺得本次分享的文章對您有幫助,隨手點[在看]并轉發分享,也是對我的支持,
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/354672.html
標籤:其他
