Close

Adventures in robotics

olaf-baeyensOlaf Baeyens wrote 10/18/2016 at 21:20 • 1 min read • Like

My adventures in building the Thor robot.

https://hackaday.io/project/12989-thor

Like

Discussions

Olaf Baeyens wrote 10/31/2017 at 19:34 point

* V6 Gold HotEnd:  CHECK

* Fusian 360 installed: CHECK

* 5 none stop of holidays: CHECK.

* Smoke test: NO SMOKE!

* Checking the 3D printer (new heater cartridge.... ongoing.)

  Are you sure? yes | no

Olaf Baeyens wrote 10/29/2017 at 02:00 point

Another very important C++ tip when you develop for speed:

C++ will optimize your code to assembler instructions and there is a big penalty in CPU cycles and memory caches when it has to jump. So you always put your most used code in the upper part and if, never in the else part.

Example:

void loop() {
    // Note: For speed we always go for the condition that is used the most because we try to avoid jumps
    // The else part is used for the condition that is used less or when speed is less important like an error
    // This line: "thor_engine->loop();" is the most important part that needs the fastest speed.    

if (arduino) {
        if (thor_engine) {
            if (is_initialization_ok) {
                thor_engine->loop();
            }
            else {
                // Thor engine initialization failed, blink like hell
                arduino->debug_led_pulse(100, 100);
            }
        }
        else {
            // Thor engine fails, blink like hell
            arduino->debug_led_pulse(20, 20);
        }
    }
    else {
        warn_massive_failure();
    }
}

This next code is slower even though it is more readable.

Example

void loop2() {
    if (!arduino) {
        warn_massive_failure();
        return;
    }

    if (!thor_engine) {
        // Thor engine fails, blink like hell
        arduino->debug_led_pulse(20, 20);
        return;
    }

    if (!is_initialization_ok) {
        // Thor engine initialization failed, blink like hell
        arduino->debug_led_pulse(100, 100);
        return;
    }

    // Everything ok, so execute the code
    thor_engine->loop();
}

In the Thor code we go for speed even though the code will look less readable and even weird.

  Are you sure? yes | no

Olaf Baeyens wrote 10/28/2017 at 20:51 point

For any new C++ developers out there these clues may save you lots of headaches.

1. Deleted objects only deleted them when they are none null.  After you deleted them assign that pointer to null. This helps when you get in an error state and need to recover from it. The fact that you put them to null means that they are already handled.

Example:

Diagnostics::~Diagnostics() {
    if (debug_led_ptr_) delete debug_led_ptr_;
    debug_led_ptr_ = nullptr;
}

2. Objects you create inside your class must always be deleted. But objects that were created and passed on as a reference should never be deleted. In big projects your code can become messy but if you keep the "The one that created it, must also delete it" you never get a double delete or an object that was already deleted somewhere else but the pointer you use now is not valid anymore.

example:

Diagnostics::Diagnostics() {
    this->debug_led_ptr_ = new LEDBlinkCore(1000);
    debug_led_external_ = true;
}

Diagnostics::Diagnostics(LEDBlinkCore* debug_led) {
    this->debug_led_ptr_ = debug_led;
    debug_led_external_ = false;
}

Diagnostics::~Diagnostics() {
    // Only delete if we created it
    if (debug_led_external_ && debug_led_ptr_) delete debug_led_ptr_;
    debug_led_ptr_ = nullptr;
}

3. When using pointers, you must also delete the allocated memory to these pointers. The clue is to always delete them in reverse order as you have create them. The reason is that one event/callback may still be triggered when you are busy deleting them. But having a reverse delete order you never get a trigger into an already deleted pointer objects.

Example:  (look at the reverse destructor delete order)

thor_engine::thor_engine() {
    nextion_usart_ptr_ = &Serial1;
    debug_led_external_ = false;

    motor_processor_ptr_ = nullptr;
    nextion_driver_ptr_ = new nextion_driver(nextion_usart_ptr_);
}

thor_engine::~thor_engine() {

    if (nextion_driver_ptr_) delete nextion_driver_ptr_;
    nextion_driver_ptr_ = nullptr;

    if (motor_processor_ptr_) delete motor_processor_ptr_;
    motor_processor_ptr_ = nullptr;

    if (debug_led_external_ && nextion_usart_ptr_) delete nextion_usart_ptr_;
    nextion_usart_ptr_ = nullptr;
}

  Are you sure? yes | no

Olaf Baeyens wrote 10/28/2017 at 01:17 point

The C++ code for the Thor-Ultratronics firmware finally took a more professional  touch. Yes I am adding naming conventions and also comments, readme's,....

Not many developers I encountered put comments in their code, but I have learned that by doing this, you are forced to think about your code and make it more readable. If you can't describe what this method do then you have a design flaw.

Comments inside the code blocks I tend to avoid because it forces me to choose better names for the variables and methods.

The C++ code is OOP! Even though callbacks in Arduino are not OOP friendly. As the code base grows it is easier to control the library.

This code is not for release yet at this stage.

  Are you sure? yes | no

dannyvandenheuvel wrote 10/28/2017 at 08:33 point

Today I gone write some code in arduino. It will be a standalone controller for my finished smartrobot. On November in will be on the expo in Kortrijk 'Prototyping 2017'. The next coming weekend there will be a visite from leapfrog themselves to take a reportage, I was surprised they called me a few days ago :-)

I'm just like you, all my sources are well documented as well.  I hated it to edit some source without any comment. Editing some source afterwards will be a pain in the ass to know exactly how it was written.

Keep up the good work! Like always I follow you on your journey :-)

  Are you sure? yes | no

Olaf Baeyens wrote 10/28/2017 at 17:54 point

Prototyping 2017, I saw that card near your monitor in your last video.

It is a slow journey but i have more free holidays coming up.

  Are you sure? yes | no

Olaf Baeyens wrote 10/27/2017 at 20:46 point

A bit stuck with 3D printing, I damaged my heater cartridge wiring when changing the printer head.

Steep learning curve to learn all about extruders.  I could change the heater cartridge as I discovered but I am going to go for higher quality components ready for tougher jobs and higher chance of success.  (I hope)

The nice thing about this Prusa original MK2 is that I can upgrade all parts the way I like.

The biggest issue is that I kept on having a Thermal runaway when I used the object cooling fan. I think if I go to a v6 Plated Copper Heater Block, this may help a bit. And probably a silicon sock.

I live in an apartment, I cannot leave this printer alone so print jobs must stay below 12h. Big print jobs means weeks planning ahead (I also have a social live and work)

I already have the Olson Ruby Nozzle, that one should be indestructible. Carbon nanotubes filament, here I come!

  Are you sure? yes | no

Olaf Baeyens wrote 10/17/2017 at 21:42 point

Just hunted down a hard to find bug in my C++ code for the Thor robot disrupting my serial communication.  Took me 3 hours to find this one.

I only succeeded by going back to a previous know state (backup) and step by step reimplement code changes. Debugging an Arduino where all you have is this lousy blinking LED is interesting.

The source code is now migrating from prototype to professional functionality that can grow. I go for C++ class structure, harder to create but easier to extend and maintain. (Done that for 5+ years professionally)

Passing on the Serial port to the class was a touch one to crack. I don't want compiler constants that fixes the configured Serial port, I want one that can be dynamically modified.

  Are you sure? yes | no

Olaf Baeyens wrote 10/02/2017 at 02:14 point

Finally had some time to redesign the Nextion HMI display communication to fit my need.

The original library was not that bad but too generic and slow for what I need when you have a lot of controls on one display. I need simplicity and a small footprint.

It basically reduces to this usage.
One callback and in that callback you know what page, what button and if it is pressed or not.


void ButtonPress_Callback(byte page, byte button, byte pressed) {
    if (page == 1) {
        if (button == MotorEnable_E0_Left_ID) {
            if (pressed != 0) {
                digitalWrite(LED_BUILTIN, HIGH);
            }
            else {
                digitalWrite(LED_BUILTIN, LOW);
            }
        }
    }
}

void loop() {

    display_loop(ButtonPress_Callback);

}

This is only the starting code, from here on we will build on it.

Also note that there is no complex code setup.

  Are you sure? yes | no

Olaf Baeyens wrote 09/24/2017 at 00:37 point

Print has completed in 11h30. Looks perfect and all in one go.

  Are you sure? yes | no

dannyvandenheuvel wrote 09/24/2017 at 08:00 point

Is this realy that good (PETG) I never used it.

  Are you sure? yes | no

Olaf Baeyens wrote 09/24/2017 at 17:05 point

When I find time I will try it on one of the parts that I guarantee always fail.

  Are you sure? yes | no

Olaf Baeyens wrote 09/23/2017 at 14:10 point

Finally started to print Dannies Thor+ V2.03 in PETG.  Expected 11 hours print.

Last 3 weeks was one hell of a ride, my partner got sick then I got sick and finally preparing to change client which means learning stuff and not have time for any hobbies. 

But we are 2 hours into the print and everything looks nice no sign of warping yet.

  Are you sure? yes | no

Olaf Baeyens wrote 09/16/2017 at 02:33 point

The PETG print is a complete success! No warping whatsoever and it feels like ABS just more flexible. I am feeling confident that I can pull off the complex big prints that will take 12H. This one is postponed to next week, I cannot leave the printer alone since I live in an apartment.

  Are you sure? yes | no

Sepio wrote 09/17/2017 at 14:27 point

Nice. I am currently redesigning both bodies of ART 4, the covers of art 5 and the gripper parts.

  Are you sure? yes | no

Olaf Baeyens wrote 09/15/2017 at 20:44 point

Finally have some time to test PETG. The long base print is not for this weekend, not enough time so I went for these parts that Sepio created.

https://hackaday.io/project/26341-building-the-thor-robot-arm/log/66895-day-15-redesign-of-art56motorcoverring

  Are you sure? yes | no

Olaf Baeyens wrote 09/02/2017 at 12:04 point

The 3D printing will be delayed for a week, my partner is ill and I don't want any fumes in my apartment.

  Are you sure? yes | no

Olaf Baeyens wrote 09/01/2017 at 22:01 point

Preparing for Dannies Thor+ V2.03 planned tomorrow in PETG.
An expected 10H job, and this time I will shut off the object cooler to prevent an Thermal runnaway..

  Are you sure? yes | no

Sepio wrote 09/03/2017 at 10:48 point

10h for the new base? It did cost me 1d 9h with a 0.4mm nozzle and 50% infill.

  Are you sure? yes | no

Olaf Baeyens wrote 09/03/2017 at 11:59 point

I use 20% infill. It is what Simplify3D calculated. Normally it is about 1 hour off.

  Are you sure? yes | no

Olaf Baeyens wrote 08/31/2017 at 17:37 point

For the record: I work as a consultant for a consultancy in Belgium. It appears that end of September I may become available for another client if the contract does not get renewed in time (I have no desire to change my consultancy).

Anything in the circle between Antwerp, Brussels, Leuven I may be interested in. A free parking space is what makes me very happy!

My LinkedIn profile will get updated this evening.

I post this here because it would be sooo cool to have something in the Robot/AI/Drones electronics technology.

(PS realize that up till now I was always hired at the spot in record short time. The window to get me is short)

  Are you sure? yes | no

Olaf Baeyens wrote 08/30/2017 at 21:33 point

Ah yes, people that used to develop in other languages, there is no try-catch or try-finally available for the Arduino you spoiled brats ;-)

Either we develop our software in such a way that it can't fail, or we invent our own version of a try-catch.  For this Thor project we will do probably both.

  Are you sure? yes | no

Olaf Baeyens wrote 08/30/2017 at 21:22 point

Interesting the Nexion nexLoop()  that is executed in the Arduino code seems to be an endless loop.  It is not returning control for more none Nexion HMI code. They assume that the HMI is the only controller and callbacks trigger the execution.

Let's tackle that part and redesign it to be Thor friendly. :-)
I want every single CPU cycle that would be wasted waiting for the next character available for preparing the next set of motor steps to be executed.

I still need to master one vital part in the code I have in vision. The stepper motors steps are fed by a buffer that guarantees a continuous stream executed by an hardware interrupt with a predictable speed. This guarantees me that the stepper pulses are perfectly timed isolated by software delays.

Outside the interrupt you have the normal Arduino loop that prepares these buffers and handle the display. The missing link I still not have solved is how to prevent the interrupt starting to execute code that is incomplete. I need a mechanism of locking, but preferably a mechanism that avoids me to user locking.

For de Arduino I did find a command to block the Interrupt from firing but the Ultratronics board is a Arduino Due it is a bit more complicated. The code did not compile.

This blow seems to function but I did not manage to verify if this is trustworthy.

#define interrupts() __enable_irq()
#define noInterrupts() __disable_irq()

Code:

    interrupts()

   <Insert your thread dangerous code here>

   noInterrupts()

  Are you sure? yes | no

Olaf Baeyens wrote 08/29/2017 at 20:42 point

AHA! Looking into the Nexion Arduino implementation and I notice that the reason why I only react on the button up is because I use "MotorEnable_Z.attachPop(MotorEnable_Callback, &MotorEnable_Z);"

There also exists a attachPush() that most probably reacts on the button down press. I have no time to test this.

I mistakenly assumed that this was a LIFO stack where push and pop are keywords.  I sadly have no free time to test this.

  Are you sure? yes | no

Olaf Baeyens wrote 08/28/2017 at 21:46 point

I have been thinking in the car on the Nextion button "up" press that calls the callback. I don't like that but the Nextion Arduino code library seems to be developed for that. Next time I will focus on that so when I push the Nextion display button the robot detects it and starts the motor. And when I release the button then the robot should stop.

I have seen the "down" and "up" signals being transmitted so I should be able to trigger on that.

The Thor implementation I design is stepper motor pulses is driving by a predictable interrupt that can reach 5 revolutions per second at 32 micro-steps (and 8 motors simultaneously) .  It is mask driven and the button press down would activate the mask and the button up would disable the mask.

This way, the callback is nothing more than "setting a byte" in number of CPU cycles once at the start and once at the end.

  Are you sure? yes | no

Sepio wrote 08/30/2017 at 12:08 point

I like that idea. If I understand it correcly you use one byte. One byte is 8 bits = 8 motors. Bit 1 on = Motor 1 on.

How do you control the direction of the motor? Do you use another byte? 1=Clockwise, 0=Counter clockwise.

Something like

0000 0001 0000 00001

First byte, last bit = Motor 1  on, Second byte, last bit = Motor 1 Clockwise.

  Are you sure? yes | no

Olaf Baeyens wrote 08/30/2017 at 16:52 point

I have one bitmask for every motor to enable/disable. bit 1 = enable, bit 0 = disable. So you simultaneously enable/disable all motors in one command.

The direction is the same, the bitmask for the direction van be 1 = clockwise, 0 = counter clocwise (just as example).

Then I have a third bitmask that sends the pulse to the motor. I do not need send the enable and the direction anymore only the step pulses as long as I do not change direction.

The cool thing about this is that between point X and Y you move in one direct line so you will never have a directional change during that movement. You only change speed for every motor by alternating the bit for the motor.   10101010  v 100100100 (Only the bit 1 counts as a step forward, 0 means no step forward)

  Are you sure? yes | no

Olaf Baeyens wrote 08/28/2017 at 21:35 point

I recently got a question asked why I did not pursue with RealRTOS, why do I reinvent the wheel? The answer is that RealRTOS would prevent me to develop the solution I have in my mind.  The focal point in my mind is the mass at the hand that needs to be moved. That is the primary focus of the robot.

In order to do this I need to develop the technology that does not exist in the open community.  RealRTOS would give fast results but I would get stuck as the project grows. The RealTOS solution would also make the code enormous complex and harder to handle in the end.

The most important part is the tip of the part that moves the mass. You have to design the code seen from the mass. All angles and step pulses and timings are a consequence of that.

All I need is free time, lost of free time. And free time is coming in the next months....

  Are you sure? yes | no

Olaf Baeyens wrote 08/26/2017 at 23:52 point

Mystery solved the dual state and the normal button Nextion Arduino implementation reacts on the release event not a press event. The press event gets transmitted but gets ignored by the Arduino implementation.

  Are you sure? yes | no