close-circle
Close
0%
0%

Building the Thor robot

Building a 6-axis robot based on the Thor robot.
When size DOES matter!

Similar projects worth following
Why build small when you can build BIG?

I found this cool robot on Hackaday (https://hackaday.io/project/12989-thor) that is bigger than my 3D printer so that is what drew me towards it.

The fact that it is big means that it forces me to think outside the box and face challenges to print it within reasonable time, keeps it functional, find workarounds when the printing fails, make it serviceable and makes it easier to extend.

I also get great advice and printable parts that fits my 3D printer from dannyvandenheuvel at
https://hackaday.io/project/16665-thor-robot-with-addons-and-gui

Thor has now also a team that I fully support: https://hackaday.io/project/12989-thor/log/51947-thors-community-is-now-onlin

The Thor robot is going to be build on my Original Prusa i3 Mk2 printer. This printer can in theory print a 200x200x250 cm object but some part of the Thor is too big.

This big dome is called Art4Body currently using ABS filament with %20 infill, 4 top and bottom layers and 3 layers as outline (190 g of filament used) and prints in 11 hours. Art4Body is basically the upper arm of the robot.

10 hours into the print I ran into an issue with my extruder and it halted the print. Unable to continue I had to find a way to print the top part. Since I use Simplify3D I could estimate the Z layer, and move the model down. Simplify3D created the gcode to start from that layer on.

I finally glued the top and bottom part. Willit be structural strong? I have no idea, but when I press that dome thenI think an Elephant can stand on it.

Printing more parts Art56CoverRing, Art32Optodisk, Art56Interface, Art56MotorHolderA and B. Infill 20% 0.3 layer height and took 3.5 hours. I think it is wise to use ABS plastic because some walls are very thin.

I do not have the motors, electronics, bearings,.....yet. Still figuring out where to order them from. The goal is to focus on the upper part first. The parts are smaller, and also more complex. It is a good way to learn to handle my 3D printer firs before getting in the bigger parts.

My Prusa i3 Mk2 has an issue between the contact of

the extruder temperature sensor and the electronics. The connector appears to have a bad contact making the temperature sensor jump during the print. I think I may need to solder that part.

Art1Body is going to be a challenge to print. It originally needed 31 hours to print, but I could bring it down to 19 hours. The biggest reason why it takes so long is the support structure to hold the dome and the lower half flat panel that also needs support structures. Losing the support structure means winning 6 hours.

I am experimenting in Simplify3D to print it in 3 parts. The top halve, the mid section and the bottom part but upside down. I have chosen the top part to be printed above the openings that will have mechanical stress.

I finally printed the bottom part of the modified Art1Body. Note that it is upside down so I avoid support structures. This party is printed in 4h45 (vs 8h if it were to printed the upside way.)

Printing the mid section took me 8h45, and the top section that took 1h30. But when I removed the supports, I broke some thinner walls.

The gluing took challenges and I had to put 10 kg of boxes on top of it to hold it into place. I finally glued the dome on top. It is a Frankenstein model but it only took 13 hours of printing.

The gluing is surprisingly strong. Next will be gluing back the thinner walls.

  • 3 × ABS filament The original Thor project tells me that 4Kg of filament si required.
  • 1 × Ultratronics Pro v1.0 This board looks very similar to the popular Megatronics board, but it much more power due to the 32-bit Atmega SAM3X8E clocked at 84Mhz. (Expect €140)
  • 1 × SunPower SPS 100P-D2 100W Dual Output Enclosed Power Supply 5Vdc 10A Power supply to provice 5V 10A and 24V 4A. (Except €100)
  • 3 × Gear Ratio 5:1 Planetary Gearbox With Nema 17 Stepper Motor 17HS13-0404S-PG5 (DE208) 3 Geared motors to rotate the arm (expect €31 each)
  • 1 × Nema 17 Stepper Motor 2A 45Ncm(64oz.in) 17HS16-2004S1 Rotates the base (expects €10)

View all 7 components

  • Programming the Nextion 7" panel

    Olaf Baeyens07/01/2017 at 21:04 0 comments

    For the Thor robot I use the Ultratronics v1.0 pro board which is basically an Arduino Due.

    The touch panel that I want to use is the Nextion 7" HMI panel.

    These are the steps that I had to take to make the panel work with the Ultratronics board.

    The panel layout is develop with the Nextion editor. Then compiled, put on a micro SD card and inserted on the display. Power off-on and it gets loaded into flash. Remove the micro SD card and power off and on then the display is active.

    The graphical layout depends on your graphical skills.

    Important to that we have these buttons marked as b0, b1 and b2. This will come back in our Arduino programming.

    Also make sure that for every button you have activated a event when the button gets released. Thsi event is then transmitted through the serial TxD and received by the Arduino.


    Get the software for the Nextion found here: https://github.com/itead/ITEADLIB_Arduino_Nextion

    Then copy it to the location of your Arduino libraries or your local project.

    The Ultratronics is an Arduino Due, and this code is not compatible for the Arduino Due simply because SoftwareSerial.h does not exist for the Due. It took some time to find what I had to do with it.

    We need to do 2 modifications

    So open file NexUpload.cpp

    Now comment out the #include SoftwareSerial.h, since this prevents you to compile for the Arduino Due.

    And since we will use one of the additiopnal UART's let's comment out the USE_SOFTWARE_SERIAL.

    Next step we will define what UART we will use so open NexConfig.h

    As described in my previous log I have hooked it to Serial1. (note the "1"). Also note that Serial (without the number should maybe defined as Serial0)

    Now how do we use it?

    In your Arduino project

    #include "Nextion.h"
    
    /*
    * Declare a button object [page id:0,component id:1, component name: "b0"].
    */
    NexButton b0 = NexButton(0, 1, "b0");
    NexButton b1 = NexButton(0, 2, "b1");
    NexButton b2 = NexButton(0, 3, "b2");
    
    char buffer[100] = { 0 };
    
    /*
    * Register a button object to the touch event list.
    */
    NexTouch *nex_listen_list[] =
    {
    	&b0,
    	&b1,
    	&b2,
    	NULL
    };
    
    /*
    * Button component pop callback function.
    * In this example,the button's text value will plus one every time when it is released.
    */
    void b0PopCallback(void *ptr)
    {
    	uint16_t len;
    	uint16_t number;
    	NexButton *btn = (NexButton *)ptr;
    	memset(buffer, 0, sizeof(buffer));
    
    	/* Get the text value of button component [the value is string type]. */
    	btn->getText(buffer, sizeof(buffer));
    
    	number = atoi(buffer);
    	number += 1;
    
    	memset(buffer, 0, sizeof(buffer));
    	itoa(number, buffer, 10);
    
    	/* Set the text value of button component [the value is string type]. */
    	btn->setText(buffer);
    }
    

    Notice in the project b0, b1 and b2 defined as in the Nextion editor.

    This code actually increments and send the number to that button. On your Nextion display you get the incremented number on that button every time you press it.

    void setup() {
    	Serial1.begin(9600);
    
    	/* Set the baudrate which is for debug and communicate with Nextion screen. */
    	nexInit();
    
    	/* Register the pop event callback function of the current button component. */
    	b0.attachPop(b0PopCallback, &b0);
    	b1.attachPop(b1PopCallback, &b1);
    	b2.attachPop(b2PopCallback, &b2);
            ...
    }

    We attach the defined buttons and callback to execute for each button.

    void loop() {
    	/*
    	* When a pop or push event occured every time,
    	* the corresponding component[right page id and component id] in touch event list will be asked.
    	*/
    	nexLoop(nex_listen_list);
    
    }

    And finally the loop that searches for events and call the callback function.

    This screen is not intended for streaming video, the fact that the HMI takes all the processing power makes he serial communication actually pretty efficient and low on CPU cycles on the Arduino.

  • Connecting the Nextion 7" panel

    Olaf Baeyens07/01/2017 at 19:14 0 comments

    The Nextion 7" HMI panel is a self contained processor that drives the display independent of what is connected to it. The displays, button, interactions are configured using a micro SD card.

    It has a bidirectional serial communication protocol Rx/Tx to tell the connected controller what button is pressed and the connected controller can then send data back that can be used to display.

    Left is the Nextion 7" HMI display with 3 buttons programmed on it.

    Right is the Thor controller (Ultrasonics v1.0 pro), which is basically an Arduino Due.

    This Ultratronics v1.0 pro board does provide 5V and 3.3V power but this 7" draws way too much current so I had to connect the 5V to my 5V power output directly.

    The power switch you see above top left is connected to the 24V. Early experiments turned out that the main power source I use is a but too powerful and when I pull the 220V power, it still has enough power to drive stepper-motors for a few seconds. I did not want to lose fingers during these few seconds so that is why this 24V cut of switch is there.

    (In the future I want an internal 24V to 5V step down inside the box. so I only have to draw from the 24V main power supply.)

    The Nextion display with test buttons.

    The numbers you see here is the number of times I pressed the button, a command was sent to the Thor controller. The Thor controller incremented a number and send it back to be displayed. This is just a test-setup to verify the complete loop.

    The Nextion displays operates on 5V. The Ultratronics v1.0 pro board runs at 3.3V. So I need a bi-directional level shifter that is able to step up,/down the Rx/TX signals.

    Note that here we swap the RX to TX and the TX to Rx.


    The Ultratronics v1.0 pro board has more than one UART and this one is using Serial1 (Don't confuse this with Serial without the number)

    I want to point out that the Ultratronics board documentation is wrong or, the pin 1 assignment on the board is in the wrong place.

    This is what the documentation tells me.

    It took some time to figure this out but pin 1 is actually TXD1, pin 2 is actually RXD1 on the motherboard.

    I drew the 3.3V for the bi-directional level shifter directly from the main board. The 5V was not enough to power the Nextion display.

    Also note that the minus of the 3.3v is at the upper position. I accidentally connected it wrongly and I saw smoke coming out from my level shifter.

  • Cooling issues

    Olaf Baeyens06/11/2017 at 20:41 0 comments

    Last time when I tested the stepper motors all next to each other next to each other the motors turned very hot to a point where I nearly burned my hand.

    That made me realize that the current cooling inside the PLA plastic may be even worse.

    Danny already gave us an adapted 3D model where a fan was added to cool down the 3 geared stepper motors.

    When I look at the solution from the top view then I see that the solution does cool down the center and right motor but no air gets at the left motor. So that one may overheat.

    Also this ventilator it does not solve the cooling of the stepper motor below in the base. since the hot air will go up not down.


    That means that I have to drill a hole in the side of the base to cool its stepper motor which is actually the most powerful one. (Seen inside the hole to the left.)

    I initially thought that I would use one 40x40 mm ventilators to blow air into the base but it turns out that the base is too low to drill a hole for a 40x40 mm. So I have to resort to the 30x30 mm ones. They are more silent but also produce less air stream.

    So it makes me think that I need 2 at opposing sides that drags in air and forces it upwards to the opening.

    Air that is drawn in through the base and forced upwards now also will be forced in the upper stage and cool the 3 geared stepper motors on all sides so we can remove its ventilator.

    Another view how the air could be forced. Those ventilators could probably be mounted inside the base.

    The only issue I have is the question if I put the ventilators in the 90 degree angle or in a tilted angle so it creates a vortex into the base. The goal is to cool the base big stepper-motor too.


    If there is a cooling expert out there, then feedback would be appreciated. :-)

  • Testing the code quality- First step: Measuring

    Olaf Baeyens05/30/2017 at 21:06 0 comments

    I am using a Saleae logic analyzer to test the code I develop. (This can be done with the $10 Chinese clone but realize that Saleae developed the software to make this possible. )

    I need a few things to know. Especially because this is 3D printed cogs and material and any accuracy has gone down the drains.

    So for the Thor control I need some measurements:

    • How many micro-steps does it take to have one stepper motor full revolution?
    • How many steps does the 5:1 geared stepper motors take for one full revolution?
    • Finally, how many steps does the stepper motors need to move a certain angle?

    You go the measurement (right side) then you can define a range you want to measure on (green arrows)

    You can now select if you want to count the positive pulses.

    Example above shows 16 pulses.

    A list of possible options to choose by pressing right click on the time measurement line.


    This is a very interesting feature because when I am going to steer the stepper motors, I want to be able to measure deviations. I want to know if the stepper motors behave as they should or if I have a software bug.

    This counting becomes tricky when I start with acceleration and deceleration. This software will help. When you deal with robotics that deals with repetitive motion, then every step must be accounted for.


  • Brainstorm: The next steps in the software design

    Olaf Baeyens05/29/2017 at 20:55 0 comments


    I on purpose do not look at other robot designs to create the software. The reason why I do this is to force my imagination to come up with my own design.

    I am at a stage where I can now move the robot. Up to 8 stepper-motors in parallel all in less than 3 µS per step. And extendable to multiples of 8 more motors in parallel if the need is there.

    The question is:

    • How do I feed the data?
    • How do I take into account micro steps?
    • How do I take into account gears?
    • How do I take into account acceleration/deceleration of the stepper motors?
    • How do I take into account inertia?
    • How do I take into account imperfections in the mechanics and especially the 3D printed plastic?
    • How do I take into account mass and inertia of the mass to be moved?
    • How do you correct for errors using feed-back sensors?

    Every single point above has an effect on how the stepper movement steps must be executed.

    Big challenges ahead :-)

    That is what I love :-)


    Observations:

    • When a stepper motor is "enabled", then it jumps to the nearest full step.
      So this induces an error.
    • The motor can skip steps, when the acceleration of the load is too fast.
      This induces an error
    • A "disabled" stepper-motor does not use power.
      However do not stand under the load!
    • A "disabled" stepper-motor for a short pulse could actually reduce friction when 2 motors work in tandem.

      This may be interesting because Thor uses 2 geared stepper-motors driving the same gear that may get out of tune and cause mechanical stress. I may for a short millisecond disable one of the stepper motors so it gets dragged back in line with the one that keeps the power.

    To be continued.

  • Development: Controlling the stepper motors

    Olaf Baeyens05/28/2017 at 21:17 0 comments

    It took hard work to map and test every single control bit but I give to the community the compressed functionality to control the 7 stepper motors simultaneously used on the UltraTronics board v1.0 which is in fact an Arduino Due on steroids.

    (Constants see below)

    The code below has had real functional testing with real motors.

    void MotorCommand::SendMotorStepCommand(byte step_command) {
        // Make the signal high
        SendMotorStepHighCommand(step_command);
        StepMotorPulseDelay2Micros();
    
        // Make the signal low
        SendMotorStepLowCommand(step_command);
        StepMotorPulseDelay2Micros();
    }
    
    void MotorCommand::StepMotorPulseDelay2Micros() {
    	// Code blow should be about 2 µS on a 84 Mhz Arduino Due
    	for (auto i = 0; i < 36; ++i) {
    		asm("nop \n");
    	}
    }
    
    void MotorCommand::SetPinModes(byte enable_command) {
    	pinMode(Z_AXIS_DIRECTION, OUTPUT);
    	pinMode(Z_AXIS_STEP, OUTPUT);
    	pinMode(Z_AXIS_ENABLE, OUTPUT);
    
    	pinMode(Y_AXIS_DIRECTION, OUTPUT);
    	pinMode(Y_AXIS_STEP, OUTPUT);
    	pinMode(Y_AXIS_ENABLE, OUTPUT);
    
    	pinMode(X_AXIS_DIRECTION, OUTPUT);
    	pinMode(X_AXIS_STEP, OUTPUT);
    	pinMode(X_AXIS_ENABLE, OUTPUT);
    
    	pinMode(E3_AXIS_DIRECTION, OUTPUT);
    	pinMode(E3_AXIS_STEP, OUTPUT);
    	pinMode(E3_AXIS_ENABLE, OUTPUT);
    
    	pinMode(E2_AXIS_DIRECTION, OUTPUT);
    	pinMode(E2_AXIS_STEP, OUTPUT);
    	pinMode(E2_AXIS_ENABLE, OUTPUT);
    
    	pinMode(E1_AXIS_DIRECTION, OUTPUT);
    	pinMode(E1_AXIS_STEP, OUTPUT);
    	pinMode(E1_AXIS_ENABLE, OUTPUT);
    
    	pinMode(E0_AXIS_DIRECTION, OUTPUT);
    	pinMode(E0_AXIS_STEP, OUTPUT);
    	pinMode(E0_AXIS_ENABLE, OUTPUT);
    }
    
    void MotorCommand::SendMotorEnableCommand(byte enable_command)  {
    	byte level = enable_command;
    
    	level = enable_command & MOTOR_BIT_Z;
    	if (level != 0) REG_PIOA_SODR = PIO_PA15;		// Z_AXIS_ENABLE set to high, bit 7
    	else REG_PIOA_CODR = PIO_PA15;					// Z_AXIS_ENABLE set to low, bit 7
    
    	level = enable_command & MOTOR_BIT_Y;
    	if (level != 0) REG_PIOC_SODR = PIO_PC1;		// Y_AXIS_ENABLE set to high, bit 6
    	else REG_PIOC_CODR = PIO_PC1;					// Y_AXIS_ENABLE set to low, bit 6
    
    	level = enable_command & MOTOR_BIT_X; 
    	if (level != 0) REG_PIOC_SODR = PIO_PC5;		// X_AXIS_ENABLE set to high, bit 5
    	else REG_PIOC_CODR = PIO_PC5;					// X_AXIS_ENABLE set to low, bit 5
    
    	level = enable_command & MOTOR_BIT_E3;
    	if (level != 0) REG_PIOC_SODR = PIO_PC8;		// E3_AXIS_ENABLE set to high; bit 4
    	else REG_PIOC_CODR = PIO_PC8;					// E6_AXIS_ENABLE set to low, bit 4
    	
    	level = enable_command & MOTOR_BIT_E2;  
    	if (level != 0) REG_PIOA_SODR = PIO_PA20;		// E2_AXIS_ENABLE set to high, bit 3
    	else REG_PIOA_CODR = PIO_PA20;					// E2_AXIS_ENABLE set to low, bit 3
    
    	level = enable_command & MOTOR_BIT_E1; 
    	if (level != 0) REG_PIOC_SODR = PIO_PC18;		// E1_AXIS_ENABLE set to high, bit 2
    	else REG_PIOC_CODR = PIO_PC18;					// E1_AXIS_ENABLE set to low, bit 2
    
    	level = enable_command & MOTOR_BIT_E0;			
    	if (level != 0) REG_PIOC_SODR = PIO_PC15;		// E0_AXIS_ENABLE set to high, bit 1
    	else REG_PIOC_CODR = PIO_PC15;					// E0_AXIS_ENABLE set to low, bit 1
    
    	StepMotorPulseDelay2Micros();
    }
    
    void MotorCommand::SendMotorDirectionCommand(byte direction_command) {
    	byte level = direction_command;
    
    	level = direction_command & MOTOR_BIT_Z;
    	if (level != 0) REG_PIOD_SODR = PIO_PD1;		// Z_AXIS_DIRECTION set to high, bit 7
    	else REG_PIOD_CODR = PIO_PD1;					// Z_AXIS_DIRECTION set to low, bit 7
    
    	level = direction_command & MOTOR_BIT_Y; 
    	if (level != 0) REG_PIOA_SODR = PIO_PA14;		// Y_AXIS_DIRECTION set to high, bit 6
    	else REG_PIOA_CODR = PIO_PA14;					// Y_AXIS_DIRECTION set to low, bit 6
    
    	level = direction_command & MOTOR_BIT_X; 
    	if (level != 0) REG_PIOC_SODR = PIO_PC2;		// X_AXIS_DIRECTION set to high, bit 5
    	else REG_PIOC_CODR = PIO_PC2;					// X_AXIS_DIRECTION set to low, bit 5
    
    	level = direction_command & MOTOR_BIT_E3; 
    	if (level != 0) REG_PIOC_SODR = PIO_PC6;		// E3_AXIS_DIRECTION set to high; bit 4
    	else REG_PIOC_CODR = PIO_PC6;					// E3_AXIS_DIRECTION set to low, bit 4
    
    	level = direction_command & MOTOR_BIT_E2; 
    	if (level != 0) REG_PIOC_SODR = PIO_PC9;		//...
    Read more »

  • Developing: Testing test patterns

    Olaf Baeyens05/27/2017 at 22:12 0 comments

    I am developing for the Ultratronics v1.0 which is a Arduino Due.

    This is done in Visual Studio 2015 and a tool: VisualMicro.

    Developing the code is not easy because I have been slamming into a wall for a few days before I started to realize that the compiler may not produce correct code for what I wanted.

    It seems that inside classes the C++ does not like "MyClass* myClass=new MyClass()" on the Arduino Due. Dynamic memory allocation seems not to function nicely. Something that would have worked correctly on a Windows C++.

    It forced me to a redesign. Less dynamic assigned memory and more static compile time determined memory.

    But I have results:

    Hooking up the logic analyzer shows the clear test pattern.Each puls is a stepper motor step command.

    Zoomed in

    Zooming even more in you clearly short pulses that are a bit higher than 2µS.

    The code that generates the puls is below. I use one bit per motor to drive the simultaneously. Then a silly loop with a "nop" in it that generates a pulse with that is a bit higher than the required 2µS

    void MotorCommand::StepMotorPulseDelay2Micros() {
        // Code blow should be about 2 µS on a 84 Mhz Arduino Due
        for (auto i = 0; i < 36; ++i) {
            asm("nop \n");
        }
    }
    void MotorCommand::SendMotorStepCommand(byte step_command) {
    	// Make the signal high
    	SendMotorStepHighCommand(step_command);
    	StepMotorPulseDelay2Micros();
    
    	// Make the signal low
    	SendMotorStepLowCommand(step_command);
    	StepMotorPulseDelay2Micros();
    }
    
    void MotorCommand::SendMotorStepHighCommand(byte step_command) {
        byte level = step_command;
    
        level = step_command & MOTOR_BIT_Z;
        if (level != 0) REG_PIOD_SODR = PIO_PD0;        // Z_AXIS_STEP set to high, bit 7
    
        level = step_command & MOTOR_BIT_Y;
        if (level != 0) REG_PIOB_SODR = PIO_PB26;        // Y_AXIS_STEP set to high, bit 6
    
        level = step_command & MOTOR_BIT_X;
        if (level != 0) REG_PIOC_SODR = PIO_PC3;        // X_AXIS_STEP set to high, bit 5
    
        level = step_command & MOTOR_BIT_E3;
        if (level != 0) REG_PIOC_SODR = PIO_PC7;        // E3_AXIS_STEP set to high; bit 4
    
        level = step_command & MOTOR_BIT_E2;
        if (level != 0) REG_PIOA_SODR = PIO_PA19;        // E2_AXIS_STEP set to high, bit 3
    
        level = step_command & MOTOR_BIT_E1;
        if (level != 0) REG_PIOC_SODR = PIO_PC19;        // E1_AXIS_STEP set to high, bit 2
    
        level = step_command & MOTOR_BIT_E0;
        if (level != 0) REG_PIOC_SODR = PIO_PC16;        // E0_AXIS_STEP set to high, bit 1
    }
    
    void MotorCommand::SendMotorStepLowCommand(byte step_command) {
        REG_PIOD_CODR = PIO_PD0;                        // Z_AXIS_STEP set to low
        REG_PIOB_CODR = PIO_PB26;                       // Y_AXIS_STEP set to Low    
        REG_PIOC_CODR = PIO_PC3;                        // X_AXIS_STEP set to Low
        REG_PIOC_CODR = PIO_PC7;                        // E3_AXIS_STEP set to low
        REG_PIOA_CODR = PIO_PA19;                       // E2_AXIS_STEP set to low
        REG_PIOC_CODR = PIO_PC16;                       // E0_AXIS_STEP set to low
        REG_PIOC_CODR = PIO_PC19;                       // E1_AXIS_STEP set to low
        return;
    }
    

    The idea is to have pre-calculated motor bits ready in memory that can, be read at a fast rate and executed by a timer. The time above is at 32Khz, which means that a 200 Steps motor, at 32 microso-steps has about 5 revolutions per second.

    This may seem fast but the Thor robot has geared steps with a 5:1 ration and then we have another gear X:1 slowing it even more. I image think about having to have only 16 or maybe even 1 micro step per motor.


    Next step direction and enable control.


  • Motor control: The timer interrupt driver

    Olaf Baeyens05/16/2017 at 20:20 0 comments

    The code developed for Thor is intended to make the robot move like a ballet dancer. I am not controlling 1 or 2 motors but SEVEN at a time and they must all move like ballet dancers because we are moving real big masses that has inertia. Or worse, has maybe fluids.

    In order to have perfect control over acceleration and deceleration we need a predictable timing that is independent of anything else I do to calculate the next steps or check for user input.

    The whole purpose of the robot is to move! All else is secondary except for an emergency button. ON top of that primary focus is the mass it has to transport. That mass is more massive than a standard top of a 3D printer. The motor execution workflow must take that into account.

    The code to generate a timer interrupt on an Arduino Due (used by the Ultratronics v1.0 motherboard) was harder to find. It is different than an Arduino Uno/Mega.

    The code below is what drives the output you see at the logic analyzer at 32767 Hz.

    #define timerFrequency 32767
    
    // Black magic
    void startTimer(Tc *tc, uint32_t channel, IRQn_Type irq, uint32_t frequency) {
    	pmc_set_writeprotect(false);
    	pmc_enable_periph_clk((uint32_t)irq);
    	TC_Configure(tc, channel, TC_CMR_WAVE | TC_CMR_WAVSEL_UP_RC | TC_CMR_TCCLKS_TIMER_CLOCK4);
    	uint32_t rc = VARIANT_MCK / 128 / frequency; //128 because we selected TIMER_CLOCK4 above
    	TC_SetRA(tc, channel, rc / 2); //50% high, 50% low
    	TC_SetRC(tc, channel, rc);
    	TC_Start(tc, channel);
    	tc->TC_CHANNEL[channel].TC_IER = TC_IER_CPCS;
    	tc->TC_CHANNEL[channel].TC_IDR = ~TC_IER_CPCS;
    	NVIC_EnableIRQ(irq);
    }
    
    // This function is called every 1/40 sec.
    void TC3_Handler() {
    	// You must do TC_GetStatus to "accept" interrupt
    	// As parameters use the first two parameters used in startTimer (TC1, 0 in this case)
    	TC_GetStatus(TC1, 0);
    
    	motorProcessor->ExecuteStep();
    }
    
    void setup() {
    	// Start timer. Parameters are:
    
    	// TC1 : timer counter. Can be TC0, TC1 or TC2
    	// 0   : channel. Can be 0, 1 or 2
    	// TC3_IRQn: irq number. See table.
    	// 40  : frequency (in Hz)
    	// The interrupt service routine is TC3_Handler. See table.
    
    	startTimer(TC1, 0, TC3_IRQn, timerFrequency);
    }

    I have not figured out yet how to proceed from here,. Many ideas.

    It is easy to say that I want to move the 7 motors as a ballet dancers, if your driving engine is a huge PC with tons of memory and 4 Ghz CPU. The challenge is to contain it to have as much possibilities cramped into a small memory space.

    That is what made me decide to start with Thor,. It challenges me to do stuff that everyone else claim that it cannot be done. :-)


    I already have some ideas:

    • Pre-calculated motor control data before the actual move.
    • Movement patterns stored onto an sd CARD.
    • Compressing the movement data.
    • Secondary helper Arduino's used as co-processors.
    • ...

  • Motor control: faster and faster

    Olaf Baeyens05/14/2017 at 20:48 0 comments

    Testing for faster speed.

    I am using a timer interval to trigger the motor motion.

    By having a loop of "NOP" I managed to get a predictable step pulse of 2.24 - 2.32µS

    void MotorExecutionBlock::StepDelay() {
    	// Code blow should be about 2 µS on a 84 Mhz Arduino Due
    	for (auto i = 0; i < 40; ++i) {
    		asm("nop \n");
    	}
    }
    
    void MotorExecutionBlock::SendMotorStepCommand(byte step_command) {
            REG_PIOC_SODR = PIO_PC3;		    // X_AXIS_STEP set to high
            REG_PIOB_SODR = PIO_PB26;		    // Y_AXIS_STEP set to high
            REG_PIOD_SODR = PIO_PD0;		    // Z_AXIS_STEP set to high
            REG_PIOC_SODR = PIO_PC16;		    // E0_AXIS_STEP set to high
            REG_PIOC_SODR = PIO_PC19;		    // E1_AXIS_STEP set to high
            REG_PIOA_SODR = PIO_PA19;		    // E2_AXIS_STEP set to high
            REG_PIOC_SODR = PIO_PC7;		    // E3_AXIS_STEP set to high
    
    	// Delay 2 micro seconds
    	StepDelay();
    
    	REG_PIOC_CODR = PIO_PC3;		    // X_AXIS_STEP set to Low
    	REG_PIOB_CODR = PIO_PB26;		    // Y_AXIS_STEP set to Low	
    	REG_PIOD_CODR = PIO_PD0;		    // Z_AXIS_STEP set to low
    	REG_PIOC_CODR = PIO_PC16;		    // E0_AXIS_STEP set to low
    	REG_PIOC_CODR = PIO_PC19;		    // E1_AXIS_STEP set to low
    	REG_PIOA_CODR = PIO_PA19;		    // E2_AXIS_STEP set to low
    	REG_PIOC_CODR = PIO_PC7;		    // E3_AXIS_STEP set to low
    }
    
    Which gives these fine tuned motor control

    I now speed up the clock pulses (driven by an interrupt) and get a speed of 32 Khz

    The bonus is that I have at this speed only used 7.6% of the CPU processing cycles,, I still have 92.4% of available time to do something useful with the processor.

    Controlling 7 stepper motors at 32 Khz and have 92% CPU processing power to spare. Think we are on to something. :-)

    I also have been running at this speed for 5 minutes and no smoke to see.

  • A solution: More efficient stepper motor control

    Olaf Baeyens05/13/2017 at 22:42 0 comments

    Using this method we lose about 5µS per stepper motor we want to control.

    digitalWrite(X_AXIS_STEP, level);
    As sampled by the logic analyzer below.

    Each stepper motor gets a pulse 2µS later than the previous one accumulating a delay.

    This puts our robot in a wait state that could have been used for productively instead.


    But we found a way to control the Due processor directly.
    The documentation was a bit confusing and information is hard to find but this is the key.

        REG_PIOC_SODR = PIO_PC3;		// X_AXIS_STEP set to high
        digitalWrite(E0_AXIS_STEP, 0);      // Delay 2 micro seconds
        REG_PIOC_CODR = PIO_PC3;            // X_AXIS_STEP set to Low

    We use direct port mapping and write the bit directly to the PIO.

    And for 7 stepper motors we end up with something below. We lose 0.2 µS instead of

    The code that was executes is seen below.
    However it does not explain why we have differences in ending of the pulses. The smaller pulse is only 1.76 µS which may be a bit too short.

    void MotorExecutionBlock::SendMotorStepCommand(byte step_command) {
    	REG_PIOC_SODR = PIO_PC3;		    // X_AXIS_STEP set to high
    	REG_PIOB_SODR = PIO_PB26;		    // Y_AXIS_STEP set to high
    	REG_PIOD_SODR = PIO_PD0;		    // Z_AXIS_STEP set to high
    	REG_PIOC_SODR = PIO_PC16;		    // E0_AXIS_STEP set to high
            REG_PIOC_SODR = PIO_PC19;		    // E1_AXIS_STEP set to high
    	REG_PIOA_SODR = PIO_PA19;		    // E2_AXIS_STEP set to high
    	REG_PIOC_SODR = PIO_PC7;		    // E3_AXIS_STEP set to high
    
    	// Delay 2 micro seconds
    	digitalWrite(E0_AXIS_STEP, 0);
    
    	REG_PIOC_CODR = PIO_PC3;		    // X_AXIS_STEP set to Low
    	REG_PIOB_CODR = PIO_PB26;		    // Y_AXIS_STEP set to Low	
    	REG_PIOD_CODR = PIO_PD0;		    // Z_AXIS_STEP set to low
    	REG_PIOC_CODR = PIO_PC16;		    // E0_AXIS_STEP set to low
    	REG_PIOC_CODR = PIO_PC19;		    // E1_AXIS_STEP set to low
    	REG_PIOA_CODR = PIO_PA19;		    // E2_AXIS_STEP set to low
    	REG_PIOC_CODR = PIO_PC7;		    // E3_AXIS_STEP set to low
    }

    We can optimize even more because some bits are mapped to the same PIO (e.g. 4 bits to PIOC), gaining 4x40=160 ns.

    The only question is: Does the power supply and motherboard handle 7 simultaneous steps easily?

View all 56 project logs

Enjoy this project?

Share

Discussions

Kendall Meade wrote 12/29/2016 at 17:15 point

This is both really impressive because of the skill of the printing and somewhat nail-biting for me to see the gears and so on simply being printed- I think it's going to be a lot better, eventually, to print the body/case of the robot and the suitable structures in it to be able to mount standard off-the-shelf hardware like bearings, gears, and machined parts that are stress-critical.

All of  this is impressive, keep it up.

  Are you sure? yes | no

Olaf Baeyens wrote 12/29/2016 at 17:34 point

Thanks

  Are you sure? yes | no

Alex Martin wrote 12/23/2016 at 04:46 point

Your printing skill is admirable!

  Are you sure? yes | no

Olaf Baeyens wrote 12/23/2016 at 18:00 point

Lots of failed prints! It is surprisingly hard to print successfully. But at a certain point your printer has been finetuned where printing eventually starts to become predicable.

This Thor project already took up 134 hours of printing time.

  Are you sure? yes | no

Olaf Baeyens wrote 10/23/2016 at 20:29 point

I looked at the parts list of Thor and I notice way way too many different types of screws. It is not a problem for seasoned robot builders but it is a problem when you start fresh building robots and have to find the screws to order. 

I think 3 different lengths would be nice that is used consistent.

I also appear to need different lengths of rods. This is harder to find, so for the first trial I may have to resort to 3D printed.

  Are you sure? yes | no

Olaf Baeyens wrote 10/23/2016 at 20:11 point

I knew I was missing something, the bottom of the upper arm. 

It appears to be the same objects as Art1Body. That is a touch job since I can't make it below 19 hours of printing.  Now I have to do it twice.  

The support structures to print it uses up 6 hours. Getting rid of these support structures by redesigning might solve this. Printing the bottom part upside down and the top normal and then find a way to glue them together may get rid of the support structures. But I that is in a next stage when I have learned how to design my own drawing.

  Are you sure? yes | no

Olaf Baeyens wrote 10/22/2016 at 12:59 point

Printing more parts Art56CoverRing, Art32Optodisk, Art56Interface, Art56MotorHolderA and B.  Infill 20% 0.3 layer height and took 3.5 hours. I think it is wise to use ABS plastic because some walls are very thin.

I do not have the motors, electronics, bearings,.....yet. Still figuring out where to order them from. The goal is to focus on the upper part first. The parts are smaller, and also more complex. It is a good way to learn to handle my 3D printer firs before getting in the bigger parts.

My Prusa i3 Mk2 has an issue between the contact of the extruder temperature sensor and the electronics. The connector appears to have a bad contact making the temperature sensor jump during the print. I think I may need to solder that part.

  Are you sure? yes | no

Olaf Baeyens wrote 10/19/2016 at 20:48 point

This big dome is called Art4Body currently using ABS filament with %20 infill, 4 top and bottom layers and 3 layers as outline (190 g of filament used) and prints in 11 hours. Art4Body is basically the upper arm of the robot.

10 hours into the print I ran into an issue with my extruder and it halted the print. Unable to continue I had to find a way to print the top part. Since I use Simplify3D I could estimate the Z layer, and move the model down. Simplify3D created the gcode to start from that layer on.

I finally glued the top and bottom part. Will it be structural strong? I have no idea, but when I press that dome then I think an Elephant can stand on it.

  Are you sure? yes | no

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates