"廢物利用"也抄襲——廢舊噴墨打印機和光驅DIY"繪圖儀"


    很長時間沒有寫博客,因為各種各樣的事情占去大塊時間,只有零碎時間偶爾在CSDN逛逛也偶爾回幾個帖子。很久以前就看到一些光驅DIY雕刻機之類的,很是向往,最近這幾天得閑就TB了一套Arduino UNO R3實驗套件,也實踐了一番這種單任務平台,從點亮個LED翻到步進電機就再也忍不住了,於是狠狠的操作了一番ULN2003和28BYJ48,好在經過對度娘的一番拷問,了解了不少東西,基本了解了各種所需的基本知識,其中比較需要自己實驗的方面列出來,以供大家參考:

一、步進電機、驅動和接線

        買了兩個L298N來驅動光驅步進電機,其中in1-in4就是接Arduino程序里面定義的引腳1-4,電源采用12V供電。我拆的兩個光驅電機(拆了3個其中一個是無刷的),具體接法可以自己試一試,連接兩個引腳之后轉動有阻力的就是一組,有萬用表測一下也可以的。我這兩個光驅電機都是依次排列的,即12、34兩組,所以接的時候依次接L298N的ou1-ou4即可。唯一需要注意的地方就是L298N的GND接到Arduino的GND上。

二、給Arduino編程

    用c寫代碼還是蠻別扭的,就像你開了這么多年自動擋,讓你開手動檔能不能走起呢?當然能,就是得光想着離合那點事。這里主要考慮的就是繪圖的話那2k的內存不好辦,所以還得上位機控制,所以整個程序框架建立的時候就是服從上位機指令;然后就是電機工作的平穩性,如果電機帶着整個機械機構在那廣場舞,那激光指不定射誰一眼呢(雖然我打算綁只圓珠筆搞定),所以平穩還是要的,也就修改了電機庫使它從4拍變為8拍並且增加了S型加速。所以說,驅動細分不細分的,軟件實現就可以了,畢竟我們的要求不高,2k內存還是綽綽有余的。

1、與上位機通訊

    發送消息很簡單:

//請求數據
void RequestData() {
    Serial.println(r_RequestData);
    delayMicroseconds(2000);        //16000000/96000=1666.66666
}

注意別粘包了就好。而且發送的字符盡量少,1個字節可以表示二百五還多的命令很夠用了。

    接收消息也不難,分類處理一下就可以了:

//板子初始化操作后進入的循環執行函數。
void loop() {
    if (Serial.available()!=0) {
        msgLen = Serial.readBytes(msgBuff, msgBuffSize);        //讀取消息
        if (msgLen > 0) {
            CommandParsing(msgBuff);                            //處理消息
        }
    }else{
        RequestData();                                            //請求數據
    }
}

void CommandParsing(char buff[]) {
    if (buff[0] == c_Stop) {
        DoStop();
    }else if (buff[0] == c_xForward || buff[0]==c_xBackOff) {
        DoxMove(buff[0], ToInt32(buff));
    }else if (buff[0] == c_yForward || buff[0] == c_yBackOff) {
        DoyMove(buff[0], ToInt32(buff));
    }else if (buff[0] == c_zUp || buff[0] == c_zDown) {
        DozMove(buff[0]);
    }else if (buff[0] == c_xSpeed) {
        xSpeed = ToFloat(buff);
        Serial.println(xSpeed);
        xStepper.setSpeed(xSpeed);
    }else if (buff[0] == c_ySpeed) {
        ySpeed = ToFloat(buff);
        yStepper.setSpeed(ySpeed);
    }else {
        Serial.println(r_UnknownCommand);
    }
    memset(msgBuff, 0, msgBuffSize);
}

我的接收緩沖區只有5字節,也就是說每個命令都一樣長——5字節,這可以讓代碼簡單一些,看了一下Serial的源碼,里面設置了接收緩沖區64字節,所以上位機可以一口氣發10個命令,再多就被舍棄了,還是等待下位機請求之后再發才保險。

2、修改Stepper庫

    絕大部分還是保留原來的內容,只是稍作修改,在Stepper_CDROM.h中:

const int number_of_steps=8;      // total number of steps this motor can take

unsigned long CurDelay;
    unsigned long GetSModelLine(int StepCount,int StepLeft);
    float sSpeed[8] = {16.6667, 7.1429, 4.1667, 2.5, 1.6667, 1.3158, 1.1628, 1.0638};

把原來構造函數的第一個參數修改為常量8,后面也跟着修改了setSpeed函數的實現:

//設置每分鍾轉多少步
void Stepper_CDROM::setSpeed(float whatSpeed)
{
    this->step_delay = 60L * 1000L * 1000L / this->number_of_steps / whatSpeed;
}

就如注釋的一樣,設置的速度是每分鍾的步數而不是轉數,其實這個值最后還是要不斷的調試得出,我最后確定了用這樣一個數值:

float ySpeed = 4864;            //y軸步進電機每分鍾步數的最大值

而和原來代碼格格不入的變量名就是我搞出來的了,為了計算S型加速曲線,實際上只有8步速或減速過程,這些數值是最高速時的時間間隔的倍數。雖然這樣做要比Exp函數(S型曲線的原函數計算非常耗時)來的快的多得多,但是由於我沒有這方面的經驗,所以步數和加速度可能很不理想。但是無論如何,我修改了原來代碼的內容,使用了我的時間間隔來代替原有間隔,做為萌新看起來可能還不錯:

void Stepper_CDROM::step(int steps_to_move)
{
    int steps_left;
    if (steps_to_move > 0) {
        this->direction = 1;
        steps_left = steps_to_move;
    }
    if (steps_to_move < 0) {
        this->direction = 0;
        steps_left = -steps_to_move;
    }
    int StepCount = steps_left;

    this->CurDelay = GetSModelLine(StepCount,steps_to_move);
                                                        // 到達延遲時間后轉動一步,直到轉動全部步數。
    while (steps_left > 0)
    {
        unsigned long now = micros();
        // 計算延遲是否到達
        if (now - this->last_step_time >= CurDelay)         {
            // 記錄本次轉動時間:
            this->last_step_time = now;
            // 根據方向設置當前拍:
            if (this->direction == 1)
            {
                this->step_number++;
                if (this->step_number == this->number_of_steps) {
                    this->step_number = 0;
                }
            }
            else
            {
                if (this->step_number == 0) {
                    this->step_number = this->number_of_steps;
                }
                this->step_number--;
            }
            // 記錄剩余步數:
            steps_left--;
            // 運行電機
            stepMotor(this->step_number % 8);
            //計算下一個延遲
            this->CurDelay = GetSModelLine(StepCount, steps_left);
        }
    }
}

unsigned long Stepper_CDROM::GetSModelLine(int StepCount,int StepLeft)
{
    if (StepCount < 16) {
        return this->step_delay*2;            //不足16步則無法完成一次加速和一次減速,以1/2最高速度運行。
    }
    if (StepLeft <= 8) {
        return this->step_delay * this->sSpeed[StepLeft-1];            //最后8拍倒序執行即減速。
    }
    if (StepCount - StepLeft < 8) {
        return this->step_delay * this->sSpeed[StepCount - StepLeft];    //前8拍順序執行即加速。
    }
    return this->step_delay;                                        //前后8拍之間的最高速度運行。
}

紅的的行就是應用我的時間間隔的地方了,而下面的自定義函數就是計算過程,這只需要查表就可以了,當然,我懶到對於不能進行完整加減速的過程簡單粗暴的用了一個半速。但無論如何,經過各種調試,現在這個光驅里拆出來的架子上面的機械機構運行時速度很快,聲音很小;當然,還有更重要的一點,我測試的結果是500拍就差不多從一端走到另一端共3.8cm、12圈多一點,所以這個電機大約是40步轉一圈,細分8拍之后,每一拍大約0.076mm,這個精度也可以了,但是如果用原來的庫進行4拍驅動就只能達到0.152的精度,走6拍差不多1mm了,很明顯的鋸齒有木有。之前上傳的程序有一點問題,已經修正了。下面添加一個光驅電機8拍的順序:

1000、1100、0100、0110、0010、0011、0001、1001。1表示高電平,0表示低電平。

這幾天又看了看A4988驅動,准備入手兩三塊,這個驅動編寫程序要簡單很多。

3、上位機程序

    這下開上自動擋的趕腳又回來了,很簡單的封一個類就可以:

    Private Enum Command As Byte
        c_Stop = 255        '暫停
        c_Continue = 254    '繼續
        c_xForward = 1      'x軸前進
        c_xBackOff = 2      'x軸后退
        c_yForward = 3      'y軸前進
        c_yBackOff = 4      'y軸后退
        c_zUp = 5           'z軸抬起
        c_zDown = 6         'z軸落下
        c_xSpeed = 7        'x軸速度
        c_ySpeed = 8        'y軸速度
    End Enum

    Private Enum Request As Byte
        r_UnknownCommand = Asc("d")     '未知命令
        r_RequestData = Asc("e")        '請求數據
    End Enum

    Private WithEvents mPort As SerialPort
    Event RequestData(msg As String)

    Sub New(PortName As String, BaudRate As Integer)
        mPort = New SerialPort(PortName)
        mPort.BaudRate = BaudRate
        Try
            mPort.Open()
        Catch ex As Exception
            MsgBox(ex.ToString)
        End Try
    End Sub

    Private Sub mPort_DataReceived(sender As Object, e As SerialDataReceivedEventArgs) Handles mPort.DataReceived
        Dim inData As String = CType(sender, SerialPort).ReadLine.TrimEnd({CChar(vbCr), CChar(vbLf)})
        RaiseEvent RequestData(inData)
    End Sub

    Private Sub mPort_Disposed(sender As Object, e As EventArgs) Handles mPort.Disposed
        Try
            If mPort IsNot Nothing AndAlso mPort.IsOpen Then
                mPort.Close()
            End If
        Catch ex As Exception
        End Try
    End Sub

    Sub SendCommand_Stop()
        WritePort({Command.c_Stop, 0, 0, 0, 0})
    End Sub

‘此處省略其他函數封裝。

    Private Sub WritePort(buff() As Byte)
        Try
            mPort.Write(buff, 0, 5)
        Catch ex As Exception
            MsgBox(ex.ToString)
        End Try
    End Sub

使用指定的端口名和波特率初始化一下端口類,定義命令和請求,然后封裝好不同的命令和請求就可以了,當然這里請求處理還不完整也為了測試方便,把請求事件那里的條件去掉了,但並不影響對整個程序結構的理解。

       雖然我的上位機程序對通訊部分進行了很多修改,但是整個框架還是這樣的。添加了一些簡單的功能,但是有些部分還不是很成熟,把經過測試沒有問題的部分說明一下:

3.1圖像的簡單處理:

      我使用了OPENCV組件(NUGET)來完成這樣幾個工作:

A、讀取和顯示圖片

       

        Dim ofd As New OpenFileDialog
        Try
            ofd.Filter = "jpg files|*.jpg|png files|*.png|bmp files|*.bmp|all files|*.*"
            ofd.Multiselect = False
            If ofd.ShowDialog = DialogResult.OK Then
                ImgSrc = New Mat(ofd.FileName)
                pnlSrc.BackgroundImage = Mat2Img(ImgSrc)
            End If
        Catch ex As Exception
            MsgBox(ex.ToString)
        End Try

這個程序很簡單,就是讀取文件然后顯示在Panel上。其中Mat2Img函數如下:

    Private Function Mat2Img(mat As Mat) As Bitmap
        Dim ms As New MemoryStream
        mat.WriteToStream(ms)
        Dim result As Bitmap = Bitmap.FromStream(ms)
        ms.Close()
        Return result
    End Function

就是利用內存流轉儲一下而已。

B、灰度化、二值化、邊緣查找

ImgGray = ImgSrc.CvtColor(ColorConversionCodes.BGR2GRAY)
ImgBinary = ImgGray.Threshold(nudThreshValue.Value, nudThreshMaxValue.Value, cmb2ThresholdTypes)
Dim pss()() = ImgBinary.FindContoursAsArray(cmb2RetrievalModes, cmb2ContourApproximationModes)

這樣就可以得到邊緣的點坐標集合,並且這些點是相連的,所以可以優化繪制代碼——按曲線繪制,比一行一行掃描看起來高大上一些。

C、二值圖也按曲線繪制

        這個功能還是利用我所熟悉的快速種子填充算法來做,只是Mat類獲取點顏色的方法不太一樣:

    Private Function FindRegionByPoint(img As Mat, mTable(,) As Boolean, rect As Rect, p As Point, color As Byte) As Point()
        Dim result As New List(Of Point)
        Dim mArray As New Queue                                 '棧——將處理點表
        Dim mP As Point = p                                     '正在處理點
        Dim mAP As Point                                        '臨時變量——可能被入棧的點
        mArray.Enqueue(mP)                                      '入棧
        Do
            If mTable(mP.X, mP.Y) = False Then                  '若未處理過
                If img.At(Of Byte)(mP.Y, mP.X) = color Then     '相同顏色則添加臨近點
                    mAP = New Point(mP.X, mP.Y - 1)                     '臨近點入棧
                    If rect.Contains(mAP) AndAlso mTable(mAP.X, mAP.Y) = False Then mArray.Enqueue(mAP)
                    mAP = New Point(mP.X, mP.Y + 1)
                    If rect.Contains(mAP) AndAlso mTable(mAP.X, mAP.Y) = False Then mArray.Enqueue(mAP)
                    mAP = New Point(mP.X - 1, mP.Y)
                    If rect.Contains(mAP) AndAlso mTable(mAP.X, mAP.Y) = False Then mArray.Enqueue(mAP)
                    mAP = New Point(mP.X + 1, mP.Y)
                    If rect.Contains(mAP) AndAlso mTable(mAP.X, mAP.Y) = False Then mArray.Enqueue(mAP)
                    result.Add(New Point(mP.X, mP.Y))
                End If
                mTable(mP.X, mP.Y) = True                               '修改為已處理
            End If
            If mArray.Count = 0 Then Exit Do Else mP = mArray.Dequeue '出棧
        Loop
        Return result.ToArray
    End Function

這個函數中使用了一個加速表,因為這個函數只是從一個點來查找一片點,有多個相連的點集的時候需要重復調用,所以加速表是重復使用的,當然以前做過一些簡單的測試,用bitarray來實現速度還要快一點。至於遍歷整個圖像的函數就不貼了,兩層循環而已,沒意思。

 

 

        以上就是這些天做的一些努力,當然了,今天100大洋的激光頭也到貨了,插在兩個光驅做的框架上做了一下測試,也遇到一些問題。激光器模組的散熱套連個硅脂都不送,貼合不緊導熱不好,在現在低負荷運行的條件下根本不發熱,也就沒處理;接的電源過了ULN2003之后電壓降很多,又沒有合適的變壓器,索性直接插了Ardiuno,所以電流很低,和A4988一起入手一塊恆壓恆流模塊准備給它供電,這樣就可以功率調節到合適的程度,當然散熱器上帶個小風扇是一定的。后面再利用打印機的字車擴大一下雕刻范圍,也許還會買個光軸做個大一點的。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM