Marlin固件的步進電機控制代碼解析

    對於入行一年的我來說,桌面級FDM打印機很多功能細節,我還沒能仔細瞭解:藏在固件代碼背後的信息內容,我也遠沒有完全消化。這正說明一個簡單的道理:消化一段代碼的最有效方法是對其移植或者重寫。換言之,僅僅是走馬觀花的瀏覽一遍,除非自己曾經編寫過類似程序,很難能透徹的領會固件代碼的精髓。特別是對Marlin這個數百名(可能不準確)開源工程師貢獻和維護的大型固件項目。

    Marlin的步進電機驅動子系統,是由中斷響應函數實現的。如果是恆定速度的步進電機驅動,實現就和這句話一樣簡單。不過對於3D打印機系統,x,y軸的運動往往速度變化非常頻繁:不僅在每次更新位置的速度不同,而且每一段位移的速度也需要經歷加速,恆速和減速階段。這是由機械系統的慣性特徵決定的:如果不同動作之間的速度銜接不好,會對電路系統造成強大的電流衝擊。特別是3D打印過程,這種速度的變化每次打印任務都數以萬計,這就意味着電路壽命將大打折扣。

    Marlin系統的速度銜接,基於leib Ramp Algorithm[1],這是一個支撐步進電機速度和控制器計數器頻率關係的算法理論,由IBM的工程師於1994年發表並於2004年在控制器內實現。這裏算法實現的關鍵在於路徑規劃器(planner)。路徑規劃器的設計意味着,程序在執行步進電機的動作之前,就已經計算好了整個過程的速度曲線。後面就只是Stepper模塊忠實地執行。

    在機器層面,這樣的設計減少了中斷響應函數中的運算量,這對於單片機來說非常友好。同時3D打印機的機械運動相比控制器的16M主頻來說要慢很多,路徑規劃器相比直接驅動,增加了一個運動緩存。這樣就能夠有效的利用控制器的高頻率,裏面蘊藏着“空間換取時間”的思想。

    在代碼層面,planner的本質在於對於一個FIFO的管理。使用C++的結構體指針數據結構能夠非常優雅的實現這個緩存的創建和管理:planner.h:

typedef struct {
  // Fields used by the bresenham algorithm for tracing the line
  long steps_x, steps_y, steps_z, steps_e;  // Step count along each axis
  unsigned long step_event_count;           // The number of step events required to complete this block
  long accelerate_until;                    // The index of the step event on which to stop acceleration
  long decelerate_after;                    // The index of the step event on which to start decelerating
  long acceleration_rate;                   // The acceleration rate used for acceleration calculation
  unsigned char direction_bits;             // The direction bit set for this block (refers to *_DIRECTION_BIT in config.h)
  float nominal_speed;                               // The nominal speed for this block in mm/sec 
  float entry_speed;                                 // Entry speed at previous-current junction in mm/sec
  float max_entry_speed;                             // Maximum allowable junction entry speed in mm/sec
  float millimeters;                                 // The total travel of this block in mm
  float acceleration;                                // acceleration mm/sec^2
  unsigned char recalculate_flag;                    // Planner flag to recalculate trapezoids on entry junction
  unsigned char nominal_length_flag;                 // Planner flag for nominal speed always reached

  // Settings for the trapezoid generator
  unsigned long nominal_rate;                        // The nominal step rate for this block in step_events/sec 
  unsigned long initial_rate;                        // The jerk-adjusted step rate at start of block  
  unsigned long final_rate;                          // The minimal rate at exit
  unsigned long acceleration_st;                     // acceleration steps/sec^2
  unsigned long fan_speed;
  #ifdef BARICUDA
  unsigned long valve_pressure;
  unsigned long e_to_p_pressure;
  #endif
  volatile char busy;
} block_t;

block_t block_buffer[BLOCK_BUFFER_SIZE];            // A ring buffer for motion instfructions
volatile unsigned char block_buffer_head;           // Index of the next block to be pushed
volatile unsigned char block_buffer_tail;

    volatile 關鍵字確保了隊列頭和隊列尾被不同函數訪問過程中,編譯器不會因爲優化和丟失更改行爲。block_t類型的指針可以方便的方位結構體內任何元素。在後面的planner規劃動作plan_buffer_line()中,代碼可以用非常優雅的結構體指針來完成。

     每當3D打印機解析到位移指令的時候,plan_buffer_line()函數就被調用。在裏面新的block_t首先被創建,並且排入隊列的隊尾;然後執行calculate_trapezoid_for_block(),計算新的block_t的關鍵速度節點及其對應的step數目;接着更新隊列裏面所有block_t的連接速度:之前隊尾的block_t的收尾速度和相關速度節點會被更新。最後調用st_wake_up()保證stepper執行的中斷打開。

    而在%steppper中,ISR函數負責在主循環之外,執行隊列裏可能存在的所有block_t。在ISR中,首先由plan_get_current_block()讀取隊列首的block_t,然後按照結構成員的step數,調用STEP_ADD和STEP_IF_COUNTER兩個宏來執行x,y,z三軸的運動。ISR每執行一次,三路各發出一個脈衝,並通過lamp ramp算法更新,根據下一個速度值來更新OCR1A寄存器來設定下一次中斷響應的週期。

    整個軟件C++實現妙至毫巔。建議大家在win環境使用eclipse來查看Cpp工程。eclipse對條件預編譯的支持非常完美,#if def能夠準確顯隱,可讀性非常好(見下圖)。全局查詢,go to define等功能也很完備。

wKioL1cMXWKjSBrkAAISW73kPIs935.png    

    另外,eclipse有豐富的快捷鍵支持看代碼:用ctrl+shift+G就能查到變量的引用;F12就能查到變量的定義。Alt+左鍵 /右鍵回到之前的鼠標位置和之後的鼠標位置;用CTRL+SHIFT+P就能尋找括號的另外一半。

    附Marlin項目



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章