Saturday, December 17, 2016

Breadboard and perfboard diagrams of an MCP73831 based Li-Ion, Li-Polymer battery charger

One of my ongoing projects is a very low-power electronics device that supposedly runs on a single-cell Li-Ion or Li-Polymer battery for a very long time, hopefully many-many months. In my idea, the battery actually could be a rechargeable one, e.g. LIR2032 or LIR2450 button cell, and I might also be able to recharge them during these months from a small solar panel, so ideally it runs "forever" with a single battery.

I looked for charger circuits and found out there are many incredibly cheap single-chip solutions available. For some reason I do not remember any more, I picked the MCP73831 chip, which is a very small, single-cell Li-Ion, Li-Polymer battery charger IC.

To be able to experiment with the chip, I just took the typical application schematics from the specification and created breadboard and perfboard diagrams. In this post I just want to share these diagrams as they maybe useful for others as well.


Please note that this circuit has no low voltage protection. On one hand, it is very practical as you can use it to restore over-discharged batteries. On the other hand, you should be careful with such batteries. Please read this blog post about recharging over-discharged LiPo batteries.


This chip is coming in a SOT23-5 package, so it is actually too small for prototyping; first of all, it must be converted to DIP format using a SOT23 to DIP adapter board. I used a DIP10 one, because that was at home, but there are other, smaller variants (less legs) as well.


The diagrams are slightly modified. First of all, I replaced the 2K resistor with a 10K one, so it has 100mA output instead of 500mA. I also added one more LED indicating incoming power. So, the green LED is always on, the red LED indicates the chargring status.

  • Blinking: no battery / battery fault
  • On: battery is being charged
  • Off: battery is fully charged 

I created Fritzing diagrams, and also a DIY Layour Creator one for the perfboard diagram as I find it nicer than the Fritzing one:

MCP73831 LiPo charger Fritzing diagram

MCP73831 LiPo charger perfboard diagram


And finally, the realization. Two remarks: 1. I added some more connectors to the perfboard to make it more useful 2. A single-sided perfboard would be sufficient, and would look nicer, but I had only the double-sided at home

MCP73831  LiPo charger breadboard

MCP73831  LiPo charger perfboard


Share:

Thursday, November 3, 2016

Markdown rendering issues on Hackage

Just a quick post on some Markdown rendering issues I recently ran into on Hackage. They were very annoying as the markup was properly rendered on github, and I could not spot any obvious problems.

1. First header is rendered as the original markup


You get

          ## synopsis

instead of

    Synopsis


Solution: there is an invisible Unicode byte order mark in the beginning of the file. Seemingly Hackage does not like Unicode.


2. Subsequent lists are merged


You have a markup like this:

* list1_item1
* list1_item2

Something in between

* list2_item1
* list2_item2
 

Something to the end
But you get something like this:

  • list1_item1
  • list1_item2  Something in between
  • list2_item1
  • list2_item2  Something to the end

Solution: you have Windows line endings. Obviously Hackage does not like Windows line endings.


Share:

Wednesday, October 19, 2016

On the approximation of Bezier curves by circular arcs

Introduction

 

Approximating bezier curves by circular arcs, in spite of how useless it sounds regarding modern drawing APIs, has (at least) one raison d'etre. The G-code language used by most CNC machines, and also adopted by most 3D printers, can deal with linear interpolation (lines) and circular interpolation (circular arcs) only. 

I have been working recently on a simple Haskell SVG to G-Code converter, and I realized, that in spite of the simplicity of the algorithm at the end, how confusing the subject is (partly because of some not very well written research paper(s) delivered by google search on the subject). 

The algorithm explained in this post is implemented in C# and can be found at my github repository. The C# code is only for illustrating the algorithm, later on it will be integrated with my Haskell SVG to G-Code project.

The article assumes that you understand basic algebra and geometry, complex numbers, etc... 

Bezier curves

 

If you do not know what a bezier curve is, please contact to your favorite information source first. Here I want to introduce only the notations used in this post. We restrict ourselves to cubic bezier curves (it is not a real limitation, any quadratic bezier curve can be straightforwardly converted to a cubic bezier curve). P1 denotes the start point, P2 the end point, and C1, C2 the control points of the start and end points, respectively.  Because it is important for the algorithm, please remember that the line denoted by P1 and C1 is the tangent at P1, and, similarly, the line denoted by P2 and C2 is the tangent at P2.

Biarcs

 

A biarc is a pair of circular arcs (= two arcs) which have the same tangent at the connection point they meet. We will use one biarc to approximate a bezier segment which has no inflection point. A traditional biarc approximation task has four parameters: a start point, an end point, and the tangents at these points. Using these four parameters, however, is not enough to uniquely identify a biarc, we need one more parameter: this can be e.g. the connection point of the arcs or the tangent at the connection point. 

Before we start

 

This algorithm works on bezier curves without any inflection points. Thus, as a preliminary step, the inflection points must be found and the curve must be split (the parts will be approximated one by one). It is a simple task though. We just have to find the points where the second derivative of the parametric equation of the bezier curve becomes zero. It is a quadratic equation, so that can happen at no more than two points. Obviously, we will be interested in the real (not complex) solutions only and only which are in the [0,1] interval. For the details please contact with the implementation.

The biarc fitting

 

We have a simple cubic bezier curve at this point, and we want to approximate it with a biarc. For that we need one more parameter. Some research suggests[1] that in the case of bezier curves, the connection point of the arcs of the biarc should be the incenter point of the triangle denoted by the points P1, P2, and V, where V is the intersection point of the tangents at P1 and P2. Let's call this incenter point G.

For illustration, see the image at the bottom of the article. Black color is related to the original bezier curve, green color is related to the incenter point, red color is related to the approximation biarc, and yellow color is related to the arc computations as it is explained in the following paragraph.

The next step is to find the two circles on which the arcs lie. We have three clues per circle. Circle 1: its two points P1 and G, and the tangent at P1. Circle 2: its two points G and P2, and the tangent at P2

To be done, we need to find C1 and C2, the center points of these circles.

It is simple. We know the tangent at P1. C1 lies on the line which is perpendicular to this tangent and goes through P1, let's denote it by P1C. If we take the section between P1 and G, its perpendicular bisector (EC1) intersects with P1C at C1. The same method can be used to find C2.

Estimate the error and go recursive


Obviously, as you can also see on the illustration, most of the time the approximation is not close enough. Thus, after approximation, the error must be estimated, and if it is out of the tolerance range the bezier curve must be split, then the two new bezier curves must be approximated recursively until you reach an acceptable deviation. In my implementation I just simply check the distance at a certain number of points along the curve and split if the maximum deviation is not acceptable (the bezier curve is split where the deviation is the maximum as suggested by [2]). This is certainly not fast, but acceptable for my purposes.


Biarc fitting

Share:

Saturday, June 25, 2016

Mini ball launcher

It is one part of a bigger project I have been thinking about for some time now, a remote controlled turret for my kids. A video is worth 1000 words, so let's see the result first:


The design is straightforward, harnesses centrifugal force with a specially designed rotor:


The 3D files, along with the OpenSCAD source, can be downloaded from Thingiverse.

I used an R140 sized electric motor salvaged from some old toy. You can use a different motor, it is easy to change the OpenSCAD file, but you should consider the RPM of the motor.
R140 sized motors
I do not want to provide any specific number here, pick up an RPM of your taste (1. be careful, if the motor is too fast, the launcher may be harmful 2. you do not really need to consider the torque, the ball should be very light) , but I can help a bit with the calculations :

For a specific range, you can use this site to calculate the necessary initial velocity. The radius of the rotor is 35mm, that is the ball runs ~0.22m every revolution....

Have fun!





Share:

Thursday, March 17, 2016

Recycling toy model IR transmitters with Arduino (use case: Silverlit Falcon Nano)

Introduction


Falcon Nano IR Controller
My kids received a Silverlit Falcon Nano toy helicopter for Christmas. It is a fantastic contraption, but it is certainly not for small kids, especially that there are no spare parts available. Long story short, it went broken before they actually learned how to fly it properly. The actual helicopter is nice enough to hang it from the ceiling of the kids' room, but I also felt sorry for the remote controller as it actually looks very decent compared to the usual chinese RC toys. Thus I decided to reuse it for one of my future projects.

The IR receiver 


A brief research told me that the actual IR signal is travelling on a PWM carrier pulsing at a carrier frequency of (usually) 38KHz; it is nicely explained in details at adafruit. Fortunately, common IR receivers, like what I purchased, TSOP38238can turn such a PWM signal (top one on the following image) into regular digital one (bottom signal):



Original IR signal vs output signal of the IR receiver
IR signal vs output signal of the IR receiver


I take the opportunity to give here some practical advice about TSOP38238: you probably want to use some bypass capacitor close to the receiver. I had serious issues when a servo was attached to the same power rail than the receiver. I could solve this issue by using a huge (1000 µF, likely too big, but I did not have smaller) bypass capacitor. The TSOP38238 datasheet also recommends to use a bigger than 0.1 µF bypass capacitor along with a resistor of 33 Ω -1 kΩ for protection against electrical overstress.

Decoding an IR signal


The IR receiver just passes the raw data along, it still needs to be decoded. Decoding means that you take the length of the zeros and ones and try to find out the meaning of the signal by this timing information. Most of the time these signals implement some kind of well-known protocol (it is usually the case with traditional remote controllers) and can be easily decoded; sometimes we can assign a unique hash number to a given signal without any knowledge of the actual protocol. Sometimes, like in this case, the protocol must be reverse engineered.

The following picture illustrates the the NEC protocol to give you the basic idea how such a protocol looks like:

Example: the NEC protocol

Decoding the Nano's IR signal: naive approach

 

I was concerned about whether automatic hash generation could be useful in this case. It is not a traditional remote controller after all, you are supposed to push multiple buttons in the same time, not to mention that you want the values of the joysticks as consecutive - and not random - numbers.

Still, I gave it a go using the Arduino IRremote library; I thought it might be smart enough to recognize the protocol or to generate values from the signals where the individual values of the buttons and joysticks can be read as bit fields.

I ran the IRrevDemo example application, but the result was very disappointing. I got the following numbers when I should have got the same ones:
D3DB175B
1927009E
6DAD5130
B08A9DC0
D3DB175B
99C4D258
D4DB18EC
F7D2AE54
2319BCD0
32E5AF23
D3DB175B
80029B2E
E6F89EA5

Decoding the Nano's IR signal: the hard way


I did not have any choice, but to have a look at the timings and reverse engineer the protocol (I was not completely clueless, though, as I found this Silverlit protocol description, which gave me a basic idea about what to look for). This time I ran the IRrecvDumpV2 example; it provided every kind of useful information, but most importantly the timings.

The followings are two typical readings representing the same value. The number in the square brackets shows the length of the signal, the numbers annotated with + and - are the lengths of the consecutive 1 and 0 signs (+ annotates 1s, - annotates 0s) of the signal in nanoseconds.

Timing[47]: 
  +1650, -450     +250, -400     +300, -400     +300, -400
  + 250, -450     +250, -400     +300, -400     +250, -450
  + 250, -400     +950, -450     +900, -450     +900, -450
  + 900, -500     +200, -450     +850, -500     +900, -500
  + 850, -450     +900, -450     +250, -450     +200, -550
  + 150, -500     +850, -500     +150, -500     +250

Timing[47]: 
  +1750, -500     +300, -450     +300, -450     +250, -500
  + 300, -450     +250, -400     +300, -400     +250, -450
  + 250, -450     +900, -500     +950, -400     +850, -450
  + 900, -400     +250, -400     +850, -400     +950, -500
  + 950, -450     +950, -450     +300, -450     +300, -450
  + 250, -450     +900, -400     +250, -500     +300

There is quite a bit of fluctuation in the numbers, but we can make some observations that helps with the decoding:
  • The signals seemingly always contain 47 timings. Good for identifying the signal.
  • It seems that the numbers annotated with - are the same (modulo fuzziness), thus do not carry information.
  • The first timing is obviously different than the others (so much bigger). By the example protocol description, I guess it is a header bit, so it can be ignored (then again, good for identifying).
  • The rest of the timings should represent 1s and 0s. There are bigger numbers, around 850-950, and smaller ones around 150-300. Let's say that everything below 500 represents 0, the others represent 1.
According to these, I modified one of the example programs a bit. The gist is in the decodeNano method:

#include <IRremote.h>

int recvPin = 2;
IRrecv irrecv(recvPin);

void  setup ( )
{
  Serial.begin(9600);   
  irrecv.enableIRIn();  // Start the receiver
}

unsigned long decodeNano(decode_results *results)
{
  unsigned long value = 0;
  
  // Start at 2, skip header
  for (int i = 2;  i < results->rawlen;  i++) {

    // Skip even indexes
    if (i & 1) {
      int t = results->rawbuf[i] * USECPERTICK;
      value <<= 1;
      value += t > 500;
    }
  }

  return value;
}

void  loop ( )
{
  decode_results results; // Somewhere to store the results

  if (irrecv.decode(&results)) { // Grab an IR code
     Serial.println(decodeNano(&results));
  }

  irrecv.resume(); // Prepare for the next value
}

The Silverlit IR protocol for Falcon Nano

 

Using this method I finally got stable values. The next step was to find out which bits are related to which buttons or joysticks. It is very simple, you basically just push buttons one by one and try to identify which bits are changed in the result. At the end, I came up with the following bit pattern:

CCTTTTTHHHHHRRRRRLLLVVV

  • C: channel (2 Bits)
  • T: throttle (5 Bits)
  • H: horizontal direction (5 Bits)
  • V: vertical direction (3 Bits)
  • T: trim (5 Bits)
  • L: light (3 Bits)

Finally, I developed some helper functions to read the bit fields and shift their values when necessary (e.g. set the origins for the joysticks):


int getThrottle(unsigned long value){
  value >>= 16;
  value &= 0b11111;
  return value;
}

int getDirectionH(unsigned long value){
  value >>= 11;
  value &= 0b11111;
  return value-15;
}

int getDirectionV(unsigned long value){
  value &= 0b111;
  return 4-value;
}

int getLight(unsigned long value){
  value >>= 3;
  value &= 0b111;
  return value == 0b111;
}

int getTrim(unsigned long value){
  value >>= 6;
  value &= 0b11111;
  return value-16;
}

int getChannel(unsigned long value){
  value >>= 21;
  value &= 0b11;
  return value;
}


Share:

Sunday, February 21, 2016

Intel Edison MCU interrupt performance issues

Introduction


I have been thinking a while about moving from Arduino to a platform with more computational power. I want something more powerful, but having real time capabilities in the same time. My first candidate was a Raspberry Pi, it is fast and could be used along with an Arduino connected by I2C. Then I learned about Intel Edison; it is more expensive, but has lower power consumption and, more importantly, has an embedded microcontroller (aka MCU), an Intel Quark, that "adds deterministic behavior to Linux applications as a service". Thus I acquired one, but, just after my very first test, I'm seriously disappointed.

The test


As a first test, I wanted something which requires real time scheduling while easy to perform. Programming the ubiquitous HCSR04 ultrasonic range sensor seemed a trivial choice; obviously Intel also thought so as they already had a tutorial for it.

Programming such a sensor is quite straightforward. It has a trigger and an echo pin. A ping can be triggered by setting the trigger pin HIGH for more than 10 microseconds. Right after the ping impulse is sent by the device, the echo pin goes HIGH until the physical echo impulse is received. So all we have to do is to measure how long the echo pin is HIGH. The elapsed time is directly proportional to the distance. For this, we need real time scheduling as it is written in the tutorial as well:
"It’s not possible to measure the duration of such a short pulse with estimated accuracy without microsecond real-time delays. For example, the scheduler could preempt the measurement process and the measurement result will become invalid."
The only problem is that the tutorial develops a synchronous toy example what is insufficient if you want to use this expensive processor for doing more than one thing in the same time.

Interrupts on the Edison MCU


For asynchronous behavior you need to utilize interrupts on a microcontroller. After a quick search on how to do that on an Edison MCU, I also learned that there are serious performance issues related to interrupt handling (by the discussion, the MCU can flawlessly handle only interrupt rates up to 100Hz). First warning sign, but I could live with that as HCSR04 measurements can be safely performed on every ~40-50 milliseconds only (according to the datasheet). Much less than 100Hz, hopefully I do not want to use interrupts for other purposes as well in the future...

On an Arduino, you may want to use a timer to periodically check the state of the echo pin, because external interrupts are available only for specific pins (although pin change interrupts work on most of the pins). This is how the NewPing library works. However, there are no timer interrupts on the Edison MCU. Second warning sign, but I can live with that as I do not like this solution anyway.

Fortunately, Edison MCU enables external interrupts on all the pins. That sounds great, all I have to do is to attach an interrupt handler on the echo pin which triggers at both falling edge and rising edge and measure the elapsed time in between. It turns out, however, that an interrupt can be either falling edge or rising edge on Edison MCU. No problem, then I attach two interrupt handlers... Not possible on the same pin... Third warning sign.

Ok. Then I'll wait until the echo pin goes HIGH, save the current microsecs, and calculate the distance in the falling edge interrupt handler. Not optimal, but most time is spent while the echo pin is HIGH, anyway. This is how the final program looks like, it is a slightly modified version of the code developed in the Intel tutorial:

#include "mcu_api.h"
#include "mcu_errno.h"

#define TRIGGER_PIN 49
#define ECHO_PIN 48

// From HCSR04 datasheet
#define MIN_DISTANCE 2
#define MAX_DISTANCE 400

#define MAX_WAIT 10000

volatile unsigned long ping = 0;

int echo_irq(int req) {
   unsigned long echo = time_us();

   int distance = (echo - ping) / 58;
   if(distance > MIN_DISTANCE && distance < MAX_DISTANCE) {
      debug_print(DBG_INFO, "DISTANCE: %d\n", distance);
   }

   return IRQ_HANDLED;
}

void send_ping() {
   // Trigger a ping 
   gpio_write(TRIGGER_PIN, 1);
   mcu_delay(10);
   gpio_write(TRIGGER_PIN, 0);

   int i = 0;

   // Poor man's method of detecting "rising edge"
   while ((gpio_read(ECHO_PIN) == 0) && (i++ < MAX_WAIT)) {
      mcu_delay(1);
   }

   ping = time_us();
}

void mcu_main() {
   gpio_setup(ECHO_PIN, 0);
   gpio_setup(TRIGGER_PIN, 1);
   gpio_write(TRIGGER_PIN, 0);
   gpio_register_interrupt(ECHO_PIN, 0, echo_irq); // 0 means falling edge

   while(1) {
      send_ping();
      mcu_delay(500000); // wait half a second
   } 
}


The latency


At first sight it looked like working perfectly, but at second sight I realized a tiny flaw: everything appeared four centimeters further away. Something is very wrong with the program. Four centimeters means ~200 microseconds, I believe that this is caused by a such a big latency between the occurrence of the event and the execution of the interrupt handler.


Interrupts on Arduino


To double check that not I made a mistake, I ported the code above to Arduino by simply replacing the names of the API functions; no structural changes.  Needless to say, it works perfectly (I tested it on an Arduino Nano).

#define TRIGGER_PIN 3
#define ECHO_PIN 2

// From HCSR04 datasheet
#define MIN_DISTANCE 2
#define MAX_DISTANCE 400

#define MAX_WAIT 10000

volatile unsigned long ping = 0;

void echo_irq() {
   unsigned long echo = micros();

   int distance = (echo - ping) / 58;
   if(distance > MIN_DISTANCE && distance < MAX_DISTANCE) {
      Serial.print("DISTANCE: ");
      Serial.println(distance);
   }
}

void send_ping() {
   // Trigger a ping 
   digitalWrite(TRIGGER_PIN, HIGH);
   delayMicroseconds(10);
   digitalWrite(TRIGGER_PIN, LOW);

   int i = 0;

   // Poor man's method of detecting "rising edge"
   while ((digitalRead(ECHO_PIN) == LOW) && (i++ < MAX_WAIT)) {
      delayMicroseconds(1);
   }

   ping = micros();
}

void setup() {
   Serial.begin(9600);

   pinMode(ECHO_PIN, INPUT);  
   pinMode(TRIGGER_PIN, OUTPUT);
   digitalWrite(TRIGGER_PIN, LOW);    
   attachInterrupt(digitalPinToInterrupt(ECHO_PIN), echo_irq, FALLING);
}

void loop() {
   send_ping();
   delay(500);  // wait half a second
}




Share:

Sunday, January 24, 2016

Remove impulse noise from ultrasonic sonar data


Introduction


Mr. Stitson has an ultrasonic sonar mounted in the front. namely a HC-SR04; it is inexpensive, and ubiquitous, but unfortunately not quite reliable. I have a single reason for having a sonar installed, I want to keep away my boys playing the "bumping" game, what is, according to some scientists, has an unhealthy effect on robots.

Mr. Stitson - Front view with the sonar

To avoid hitting the wall, in the main loop there is some logic that, based on the current speed and the latest sonar readings, slows down the vehicle.

Noisy sensor data


Not rocket science, but it works reasonably well. until the sonar returns valid data. Unfortunately, this is not the case. Standing still, these are common sonar readings:
30
30
30
31
5
29
31
31
5
33
4
30
31
31
31
31
31
30
30
30
30
I do not care about the slight fluctuation around 30 cm, but the 4 and 5 cm readings (it is called impulse noise I believe) are showstoppers, they immediately let the robot stop. Most people would cry Kalman filter here, but that would be like using a sledgehammer to crack a nut; a simple median filter will do the job perfectly.

Implementing the median filter


Such erroneous readings appear quite often, but I did not observe them more than twice in a row. A median filter takes the median value from the last N readings (a.k.a the window size), N = 5 is enough to filter out two extremely bad readings (like we have here) in a row.

All we have to do is to sort the last five readings and pick up the third one. First, store the last readings in a cyclic buffer:
// The cyclic buffer
int lastReadings[5];
// MAX_DISTANCE will be the the result until the filter warms up
for(int i=0; i<5; i++) lastReadings[i] = MAX_DISTANCE;
// Number of readings since the beginning
int nrReadings = 0;

// Add an element to the cyclic buffer
lastReadings[nrReadings++ % 5] = sonar->read();
Next, we need a function to calculate the median value based on this buffer (do not sort the buffer itself, then you loose cyclicity). That can be done very nicely and efficiently as there exist optimal sorting network for N = 5 which uses only nine comparisons (and one can be trivially ignored as we need the third element only). There is also an algorithm for computing the median value of five elements with six comparisons, but that is anything, but nice and I want to avoid premature optimization anyway.
#define swap(a,b) a ^= b; b ^= a; a ^= b;
#define sort(a,b) if(a>b){ swap(a,b); }

int median(int a, int b, int c, int d, int e)
{
    sort(a,b);
    sort(d,e);  
    sort(a,c);
    sort(b,c);
    sort(a,d);  
    sort(c,d);
    sort(b,e);
    sort(b,c);
    // this last one is obviously unnecessary for the median
    //sort(d,e);
  
    return c;
} 

Remarks


The actual code used in the robot can be found here: https://github.com/domoszlai/robotcar/blob/master/sonarnp.cpp

Some remarks about the code. It is based on the NewPing library. The class continuously reads the sonar (every 33 milliseconds only to avoid interference with the previous measurement) in an asynchronous manner. The measure() method always returns immediately with the latest (filtered) measurement. Although the results come asynchronously using a timer based mechanism, the measurements must be initialized from the main "thread" in the loop() method. This loop() method is part of the green thread library I use (notice the Thread base class). Read the comments in the code.

Finally, some remarks to median filter. 

Median filters introduce a delay proportional to N. For N = 5, the filter is on average two values behind the actual measurements, that is ~60 milliseconds. I have to be only faster than my kids, so it is acceptable for me.  If you think you have real-time requirements, then buy a proper sonar and use Kalman filter. If you want to smooth the sensor data fluctuations, use a Kalman filter. Median filters are very efficient, much faster than Kalman filters; if you do not have any of the previous requirements, stick with the median one. 

This article has some nice figures related to the topic.

Conclusion and future work


I'm quite satisfied with the current implementation, it solves my main problem in a very efficient way with minimal effort. It may make sense, though, to smooth the sensor data as well. Furthermore,  I definitely want to implement prediction of future data. These can be done by Kalman filters. However, although, Kalman filter is an incredible tool for sensor fusion, when one can provide a proper physical model, I feel it too much otherwise. I would rather try something new, like alpha-trimmed mean filters. Or just a simple linear prediction on top of the median filtered data.

As for the sensor itself, I think ultrasonic sensors are not the best tool to avoid obstacles, they just do not provide enough information for a proper plan. I will rather try using two cameras for creating stereoscopic 3D images. Of course, than I need a proper processor, but this is my plan anyway. Lidars seem also very great, but they are a way too expensive just for having fun.

Share:

Friday, January 1, 2016

"Mr. Stitson" - A kid friendly Arduino - LEGO robot


"Dad, it is almost as super as a train"

Mr. Stitson - A kid friendly Arduino - LEGO robot


This is the second version of my robot car. The first version had some problems, most importantly the power distribution was ill-designed (more precisely, it was not designed at all). 
Mr. Stitson - A kid friendly Arduino - LEGO robot
Mr. Stitson undressed

What's new


With this second version I fixed most of the problems of the first version (and probably introduced some brand new ones), and made it more versatile adding new hardware elements:

  • The car got brand new chassis, home made from MDF wood. It is bigger, nicer and more kid friendly being also a LEGO platform. 
  • The power distribution is redesigned. The robot got a nice 3S LIPO battery, and an LM2596 based switching power regulator . The power rail is regulated to 9V, which is more than what the motors require (6V), but they endure it and run faster. 
  • The microcontroller is updated to an Arduino Mega from the Arduino Nano. More pins, more timers, more possibilities...
  • A GY80 based Inertial Measurement Unit (IMU) is added. It’s a compact module that includes a gyroscope, accelerometer, digital compass, and a barometric pressure / temperature sensor. The sensors are not used as now, but I plan to utilize some of them in the future (e.g. to let the robot going more straight).
  • Nice headlights are made from 4 power leds. I also had to develop a small driver circuit as the LEDS would draw a way too much power from the Arduino. The LEDS are installed in a nice 3D printed mount.
  • Wiring is slightly improved. I used heat shrinks and made custom length jumper wires. It already looks a way less messy and safer than the previous version, even if there is room for improvement yet.

All together, it looks and works much better than the previous version. The chassis looks kind of "crafted", especially compared to the old acrylic one, and the 9V power rail makes the robot more reliable and faster. The extra sensors makes it more like a "real" robot, and provides stable ground for future experiments. And last, but not least, the new headlights are hugely popular with the kids.

Mr. Stitson in action

LED driver


I built the LED driver on half of 2x8 cm perfboard using the following parts:

1 x RFP30N06LE N-Channel MOSFET
4 x 82R resistor (for 20mA LEDS, calculate here otherwise) 
1 x 10K resistor (pull-down)
some pin headers

LED driver schematic

List of parts and cost


The chassis is inexpensive, made from wood, I already posted about it. The robot also contains many 3D printed parts which I consider as zero cost. These parts are the following:
(The list excludes ubiquitous items like jumper wires, heat sinks, etc. It also excludes general equipment like LIPO battery charger)

Part Price
Arduino Mega ~ 6$
L298N x 2 < 2$ x 2
Motors + wheels ~ 5$
PS2 controller + receiver ~ 10$
Piezo buzzer (salvage it from an old toy) < 1$
10 mm LEDs ~ 2$ (10 pieces)
LM2596 XL4015 < 2$
HC-SR04 < 1$
GY80 IMU ~ 14$
2200ma 3S LIPO battery ~ 12$
Chassis < 20$

< 76$

Problems


The power distribution works perfectly 99% of the cases now, but the microcontroller still resets when the motors are reversed at maximum speed. The problem is with the voltage regulator as it limits the current to 2A (despite that by specification it should be able to deliver 3A temporarily). The robot draws ~1.6A at 9V at full speed, so there is no much room for the inrush current when the motors start. It is probably an easy fix by adding some decoupling capacitors, or replacing the voltage regulator with e.g. an XL4015 (or adding a second voltage regulator for the electronics creating this way another power rail).  I believe it is also possible to solve this issue from software. I eliminated the problem temporarily by regulating to 6V instead of 9V.

Future work (this robot)


The hardware works as expected, now I want to concentrate on the software (including a fix for the resetting problem) as it is the weakest point currently. First of all, the individual components (wheels, lights, sensors, etc...) should register themselves instead of being hard wired. I also want some filtering on the sensor inputs (I mean e.g. Kalman filters). It would be also nice to use a PID controller and exploiting the new sensors to make the robot going more straight (the cheap wheels and the hand made chassis renders the trajectory of the robot slightly imprecise). I also would like to make some experiments with OOSMOS to find out whether it is good enough to replace the simple green thread library used currently.

Future work (next robot)


Ad-hoc wiring harness
  • Power distribution; I want a power distribution board. Mostly to avoid ad-hoc wiring harnesses, but some fuse would be useful as well. I can also imagine the voltage regulator(s) integrated. It is really strange that there are no general purpose power distribution boards available for robotics. Especially considering how helpful they are. Actually, the multirotor community already realised that.
  • Wiring: I read somewhere that robotics is all about wiring and connectors. I could not agree more. Some of my vague ideas: 3D printed wire channels, IDC header socket connectors, custom arduino shields. I really want to get rid of these unreliable dupont jumper wires.  
  • DC motors: Better motors, more torque and also more speed. It is going to be easy regarding how crappy motors I have now. I also want encoders feeding the PID controller.
  • Wheels: Mecanum wheels...
  • Software: Instead of C/C++, I would like to use a dependently typed functional language for developing the software. I believe that robotics is exactly the field where extra type safety pays back. There are not so many dependently typed languages though. I tried Idirs, but, although I really like the language, the generated code is a way too inefficient. The other option is GHC/Haskell. It is not officially dependently typed, but its type system is powerful enough to emulate it, and the upcoming version will officially provides this feature (to the extent I need it). The problem is that Haskell is lazy, what makes it unpractical for embedded applications (in the upcoming version this is optional) and it is garbage collected what renders it unusable on microcontrollers. But, why should it run on a microcontroller anyway?
  • Computer/microcontroller: I have no problem with Arduino, but it is not powerful enough to run my Haskell code. So I need a real computer, like Raspberry Pi, but I also need real time support provided by a microcontroller. I could use a Raspberry with an Arduino in pair, but actually I think Raspberry is just not a good embedded system. Mostly, because it is not an embedded system. Raspberry Zero is closer, but still. Intel Edison seems a much better choice. Powerful enough with integrated microcontroller. Sounds just perfect. 



Share:

Featured Post

V-plotter math: coordinate transformation with rotation compensation

The most important thing to deal with in v-plotter software is the coordinate transformation. Descartes coordinates must be converted ...

Popular Posts