AN-046: Adapting Step/Dir CNC Firmware for Ramp Generators
This application note describes how to adapt a step/direction-based CNC firmware to support a ramp-generator controlled by SPI. It shows several code examples from the TRAMS board firmware and visual diagrams which help to understand the problem and to adapt the code to other projects.
Introduction
This document presents a series of code examples for how to adapt a step/direction-based CNC (Computer Numerical Control) firmware to use a ramp generator. The firmware examples derive from the TRAMS Project [1]. The TRAMS board consists of an Arduino Mega and ADI Trinamic TMC5130 motion-controller chips. The TMC5130 provides SPI (Serial Peripheral Interface) and a ramp generator.
A step/direction-based firmware continuously sends step pulses to the motors, so that they reach the corresponding position and speed for the current segment. Using a ramp generator releases the microprocessor of the line-drawing algorithm execution and transmission of step pulses. On the other hand, it has to calculate the ramp parameters (speed and acceleration) and send these once per segment to the motion controller. At the same time, a chip like the TMC5130 provides other advantages like full-microstepping and stealthChop™ low-noise chopper mode. Refer to [2] for an extended explanation of the advantages of adopting a ramp-generator solution.
Step/Direction firmware
The TRAMS firmware is a fork of Marlin [3], whose original source code is available in [4]. Marlin is a firmware for RepRap single-processor electronics, supporting RAMPS, RAMBo, Ultimaker, BQ, and several other Arduino-based 3D printers. It supports printing over USB or from SD cards with folders.
Two of the main characteristics of the Marlin firmware are two algorithms: look-ahead trajectory planning and the Bresenham’s algorithm for step/direction.
Marlin uses a jerk-type look-ahead algorithm. Without it, it would brake to a stop and re-accelerate at each corner. Look-ahead will only decelerate and accelerate to some non-zero velocity so that the change in vectorial velocity magnitude is less than the maximum jerk for the x- and y-axes. This is only possible if some future moves are already processed, hence the name look-ahead. That is the reason to use a planner queue.
The G-Code interpreter extracts the information from a G-Code segment, transforms distances in mm and feed-rates into mm/s to steps and steps/s (according to the printer settings) and stores it in the planner queue. This queue needs to have several segments before it processes them so that the look-ahead algorithm corrects the highest acceleration.
A step/direction-based CNC firmware sends step pulses to the different motors. The number of steps defines the length of the segment and the frequency defines the speed. In order to coordinate all axes and be able to draw a segment, Marlin uses the Bresenham’s line algorithm [5]. An interrupt handler executes the algorithm and decides to which of the axes the next step pulse should go, in order to approximate the straight line. The current velocity and acceleration of the movement determine the interrupt frequency.
Adapting the code to use a ramp generator
The diagram shown in Figure 1 presents the different blocks which this implementation uses. The blocks in the diagram have labels with a number (e.g., 1: G-Code Interpreter) and the steps of each block with an additional letter (e.g., 1A: Get Code Movement) in order to more easily find the related block in the diagram from a code listing and vice versa.
The main change to support a ramp generator relies on the interrupt handler (block 3). This block doesn’t need to execute a line algorithm or send step pulses anymore. However, it has to calculate the parameters which describe the ramp for every axis and configure the motion controller (e.g., via SPI) to execute it just before the next segment starts.
This could be done at the time when the current segment finishes, but the execution of the segment would take a short time comparing to the calculation of the ramp parameters in a small microprocessor. In the case of several consecutive short segments, it would result in blank time between segments, higher jerk due to full acceleration and deceleration, velocity drops, lower maximum speed and even loss of steps.
In order to avoid these problems and improve the printing, the new firmware implements an intermediate queue between the planner queue and the interrupt handler. The program continuously looks in the planner queue for ready segments and pre-calculates its ramp parameters (block 2). It stores them in the motion queue and marks the segment as ”ready” when the calculation is over. The interrupt handler only needs to send this information to the chips via SPI.
With this solution, the interrupt handler takes a ready segment of the motion queue (if any) and just needs to send the parameters to the chip through the SPI connection.
The condition for this solution to work is that the motion queue does not get empty during the print. Hence, the G-Code interpreter and the look-ahead algorithm (block 1) should work fast enough to supply the motion queue with sufficient segments.
Apart from the interrupt and the motion queue, the motion controller chips require an initialization at boot-up (block 0). Another difference lies on the homing, in case the end-stops are connected directly to the motion controller (like in the TRAMS board).
Ramp-Generator firmware blocks
This section provides listings in C for the most relevant firmware blocks which differ from the step/direction implementation. Relevant information of the interfaces between them is as well-provided. The G-Code interpreter does not change significantly, so it does not have a dedicated subsection. Section 6 presents the definitions used in the listings.
Driver initialization
The TMC5130_init() function, shown in Listing 1, runs on the microprocessor boot-up routine. Its purpose is to initialize and configure the motion controller chips of each axis.
This is the first part which uses the SPI to communicate with the chips. The TMC5130 chip works with 5-byte SPI datagrams. The first byte consists of a read/write bit indicator (the most significant bit) and seven bits for the register address. The next four bytes contain the written value (or the read value in the response datagram). An extended description of the SPI communication can be found in the SPI section of the TMC5130 datasheet [6].
If the SPI line is not a dedicated one (like in the TRAMS board), the chip-select pin of the chip which receives the current datagram must be set low during the communication, while the rest of the chip-select pins are kept high.
The most important settings written to the TMC5130 are:
- Current settings (IHOLD_IRUN register, l. 13 of the listing): ihold (reduced current during standstill) and irun (during the movement).
- Ramp configuration (l. 14-16): positioning mode (0 in RAMPMODE register), 6-point-ramps (disabled in this case, V_1 = 0), and D_1 different from zero.
- Chopper settings (CHOPCONF register, l. 19): TOff time, microstep resolution, etc.
- General configuration (GCONF register, l. 20): stealthChop™ enable, diagnostic pins, etc.
Refer to the datasheet [6] for a more detailed description of the settings.
Finally, this function initializes the motion controllers enable pins high(l. 22-37) to switch off the power stage and save energy. These pins must be set low before any movement starts.
Pre-calculation of ramp parameters
The function st_calculate() takes a segment from the planner queue, calculates the ramp parameters for every axis and appends them to the motion queue.
Segments of the planner queue contain the information shown in Listing 2, which the G-Code interpreter extracted from the G-Code commands and processed with the printer settings (e.g. steps per mm, maximum speed and acceleration).
The first step of this block is to check whether the motion queue is already full and the planning queue has any available segment (Listing 3, l. 11-20). If these conditions are fulfilled, the function starts to calculate the ramp parameters for this segments (2A in the block diagram).
/** * @brief Initialize the Trinamic D ri ve rs (TMC5130) * @param csPin chip select for spi (XAXIS, YAXIS, ZAXIS, E0AXIS) * @param i run Motor run curren t (0..31) * @param ihold Standstill current (0..31) * @param stepper_direction inverse / not inverse * @return none **************************************************************************** */ void TMC5130_init (uint8_t csPin, uint8_t irun, uint8_t ihold, uint8_t stepper_direction) { uint32_t value; value = SET_IHOLD (ihold) | SET_IRUN(irun) | SET_IHOLDDELAY (7); spi_writeRegister (IHOLD_IRUN, value, csPin); //IHOLD and IRUN current spi_writeRegister (RAMPMODE, 0x0, csPin); //select position mode spi_writeRegister (V1, 0x0, csPin); //Disables A1 and D1 in position mode, amax and vmax only spi_writeRegister (D1, 0x10, csPin); //D1 not zero spi_writeRegister (AMAX, 0xFFFF, csPin); //Acceleration spi_writeRegister (VMAX, 0xFFFF, csPin); //Velocity spi_writeRegister (CHOPCONF, 0x140101D5, csPin); //Chopper Configuration spi_writeRegister (GCONF, 0x1084 | stepper_direction, csPin); //General Configuration // initialize enable pin forgiven axis // set as output // default disable, low active switch (csPin) { case XAXIS_CS : DRV_EN_X_DDR |= (1<<drv_en_x )="" ;="" tmc5130_disabledriver="" (="" x_axis="" break="" case="" yaxis_cs="" :="" drv_en_y_ddr="" |="(1<<DRV_EN_Y" y_axis="" zaxis_cs="" drv_en_z_ddr="" z_axis="" e0axis_cs="" drv_en_e0_ddr="" e_axis="" }="" <="" pre="">
[2, section, 1.1] shows the equations for these calculations. In summary, the parameters for a positioning ramp are initial speed, maximum speed, final speed, acceleration and final position. As shown before, the planner-queue segment already contains the information for a nominal ramp, which scales down for every axis.
The scaling factor is the number of steps for an axis (e.g. steps_x) divided by the total amount of steps required by the main axis (the one with the most steps in that segment), step_event_count. This way, the main axis is not scaled down and maintains the maximum allowed speed and acceleration of the printer.
Listing 4 presents the calculations for the x-axis. Y-, z- and e-axes work the same way. The following points are taken into account:
- Don’t allow zero-acceleration even if the axis doesn’t move, so that the motor can brake (l. 9).
- Keep track of the absolute position of the axis. Add or subtract the steps for that segment depending on the direction of the movement (l. 25-32).
- Maintain a copy of the last segment. If the next segment doesn’t move in that axis, use the previous values (l. 36-38).
The next step is to determine the time that the segment is going to last with the calculated ramp (2B in the block diagram). For that purpose, the trapezoid ramp is divided into three phases: acceleration, plateau (constant speed) and deceleration. For some movements, the speed does not reach the maximum and therefore there is no plateau phase (triangular ramp). Listing 5 reflects this issue.
Each of the phases has a delay defined by basic kinematic equations shown in [2]. Each delay is multiplied by 1000 to change units from seconds to milliseconds. In this example, it is multiplied again by 2000 to match the timer clock cycles (with a timer clock of 2 MHz, 2000 ticks per millisecond).
Finally, the TMC5130_t_factor scales down the delay to match the TMC5130 internal units [6, section, 14.1]. If the TMC5130 uses a different clock rate, that frequency changes the factor as shown in Listing 6.
Interrupt handler
Once released of the parameter calculation, the interrupt handler only needs to send the information of the ramp through SPI to every motion controller. The flowchart shown in Figure 2 represents the steps and conditions to be checked in this block.
Depending on the timer clock and resolution, a software extension for longer segments is required. For example, in TRAMS, the 16-bit hardware timer with 2 MHz clock counts a maximum of 32 ms, which is clearly insufficient. Blue blocks on the diagram are essential, while white blocks are only required to extend the timer.
When the interrupt occurs (3A in the block diagram, top block in the flowchart), the first step is, if the previous segment is over, is to load a new segment from the motion queue and check if the calculation is ready (3B in the block diagram) as shown in Listing 7 (l. 18). If the queue is empty or the movement is not yet ready, the function configures the timer to check it again in a short time (1 ms in TRAMS).
The parameters are thereupon sent to the chips (3C in the block diagram). In order to reduce the duration of the interrupt, it excludes the z-axis when it does not advance any steps (l. 28, 41, 50). This is because, in usual applications, G-code commands involving the z-axis don’t involve the other ones.
Finally, it sends the new target positions to the motion controllers and the ramps start immediately.
The final step, shown in Listing 8, is to configure the timer (3D in the block diagram). The most common practice with hardware timers is that the count continues when the interrupt (timer overflow) occurs, which this explanation assumes. Hence, the timer has continued counting during the interrupt handler and the counter value is the elapsed time of the interrupt handler (including the SPI communication, if any).
If the available timer requires a software extension, the first condition to check is whether the next delay (or remaining delay) is longer than the maximum delay of the timer (l. 15). In this case, the delay (stored in a variable with a bigger size than the timer counter) is reduced by the maximum delay and the timer counts this value (minus the elapsed time in the interrupt, which has been already counted).
If the delay is shorter than the maximum (or the timer is not software-extended), the final step is to set the timer with the calculated delay minus the elapsed time (l. 25). If the delay is shorter than the time required by the processor to exit the interrupt handler, the next interrupt would not be triggered. Therefore, the timer requires a minimum delay. This value is processor dependent. The TRAMS implementation uses an arbitrary value of 0.05 ms.
Interrupt timing example
Section 4.3 described how the interrupt handler works. It included a flow chart (Figure 2) which showed the different cases according to segment length and availability and timer state. Figure 3 illustrates that explanation with a timing diagram and includes every possible case.
The left line shows the time the interrupt handler dedicates to each segment (since it’s loaded until its duration is over) and a series of external events, represented by an arrow. On the right side, each block indicates the delay of the timer. At the end of every block, the timer expires and the processor executes the interrupt handler. Dotted rectangles represent how long does the SPI communication take.
In the first execution, there is no available segment. Hence, the timer waits for wait_time and then checks if there are new segments. In this example, the print starts and several segments are available before the delay is over.
The interrupt handler loads the first segment and starts immediately to transmit the ramp parameters to the motion controller. The last parameter sent is the new target position. The movement starts upon reception. After that, the function sets the timer to the duration of the movement minus the elapsed time of the SPI communication. This way, the print does not stop during the SPI transmission, while the next SPI elapsed time will compensate the previous one. Notice that the time between two segments start is the same as the duration of the first one, although its beginning is shifted down.
This first segment lasted no more than max_timer_delay. The second one is longer though. In this case, the handler function sets firstly the timer to max_timer_delay and the next time it counts the remaining time (if it were again longer than the maximum delay, the same operation would be performed until the rest fits in the timer).
The third segment is shorter than the previous ones. If the timer counting its duration expires before the processor has completely exited the handler function, the processor doesn’t trigger the next interrupt and the timer will wait again max_timer_delay. Meanwhile, the motors will have reached the target, slowed down and will be waiting for information for a long time. The total print duration will grow and the motors will have to accelerate from standstill, causing noise and inaccuracy. In order to solve this issue, the handler function will set the timer to min_interrupt_time, which is the estimated time required to send the SPI data and exit the interrupt handler function.
Finally, Figure 4 and 5 present an important issue about ramp timing. Each one shows the ramp for two consecutive segments. The x-axis represents the time and the y-axis the velocity of the motor. The first segment starts at t1 and finishes at t2, while the second segment starts at t3 and finishes at t4.
The calculation of the segment delay (see Listing 5) determines the duration of acceleration, plateau and deceleration phases as accurately as possible. Ideally, the second segment starts at the exact same point as the first one finishes (t2 = t3). It is possible that, due to calculation or timer imprecision, there is a time gap between both segments (see Figure 4), resulting in sudden complete slow-down of the motor when the target position is reached. This is a noisy and undesired phenomenon which reduces the print quality.
If this happens, the firmware should correct the delay calculation until the second segment starts slightly before the first one finishes, as shown in Figure 5. This means that the new target position arrives before that the previous one is reached or, in other words, t3 happens just before t2. Target positions will not be completely reached with this approach, but since the ideal case is not possible in practice, and if the timing is adjusted tightly, this effect will be imperceptible and the print quality, quietness and velocity will improve.
References
[1] ”TRAMS firmware repository”, Github.com, [Online]. Available: https://github.com/trinamic/ TRAMS-Firmware. [Accessed 14 3 2017].
[2] B. Dwersteg and B. Santamaría, ”ADI Trinamic Appnote AN035: CNC Motion with Ramp Generator”.
[3] ”Marlin website,” RepRap.org, [Online]. Available: http://reprap.org/wiki/Marlin. [Accessed 14 3 2017].
[4] ”Marlin official repository,” Github.com, [Online]. Available: https://github.com/MarlinFirmware/Marlin. [Accessed 14 3 2017].
[5] ”Bresenham’s line algorithm,” Wikipedia.org, [Online]. Available: https://en.wikipedia.org/wiki/Bresenham's_line_algorithm. [Accessed 14 3 2017].
[6] ADI TRINAMIC Motion Control, ”TMC5130A Datasheet” Available: https://www.trinamic.com/fileadmin/ assets/Products/ICs_Documents/TMC5130_datasheet.pdf.
Appendix: Firmware Definitions
This section shows Listing 9 with the relevant firmware definitions which are used in the above code listings. They include register definitions of the TMC5130 and default values
Revision History
Version | Date | Author | Description |
V0.8 | 13.03.2017 | Bruno Santamaría (BS) | Structure, diagram and code snippets. |
V0.9 | 14.03.2017 | BS | References, formats for code snippets, more sections completed. |
V1.0 | 15.03.2017 | BS | Snippet descriptions and list, introduction finished, main section ”adaptation” completed, section firmware blocks written and referenced. |
V1.2 | 27.03.2017 | BS | Interrupt timing example section included. Block diagram readability improved. Title changed. Style corrections. |
V1.3 | 5.04.2017 | BS | Changed to LATEX, snippets to listings, list of figures, list of listings. |
V1.4 | 16.05.2017 | BS | Added bibliography and references. Listings without page breaks. Extended intro text. Assigned appnote number. |